"""Ujian dari sisi mahasiswa: mulai, menjawab, mengumpulkan, melihat hasil.

Penilaian ada di `penilaian.py`; modul ini hanya mengatur alur, batas waktu, dan
apa yang boleh dilihat mahasiswa (kunci jawaban tidak pernah ikut dikirim).
"""

from __future__ import annotations

import hashlib
import json
from datetime import datetime, timedelta

from flask import (Blueprint, abort, flash, jsonify, redirect, render_template, request,
                   url_for)

from keamanan import butuh, pengguna_sekarang
import penilaian

bp = Blueprint("ujian", __name__)


# ── bantuan bersama (dipakai juga oleh sisi dosen) ─────────────────────────
def ujian_untuk(pengguna: dict, ujian_id: int) -> dict:
    """Ujian yang boleh diakses: mahasiswa anggota kelasnya, dosen pemilik, admin."""
    from app import kueri
    ujian = kueri("SELECT * FROM ujian WHERE id = %s", (ujian_id,), satu=True)
    if not ujian:
        abort(404)
    if pengguna["peran"] == "admin" or ujian["dosen_id"] == pengguna["id"]:
        return ujian
    if pengguna["peran"] == "mahasiswa" and ujian["status"] != "draf" and kueri(
            "SELECT 1 AS a FROM kelas_anggota WHERE kelas_id=%s AND pengguna_id=%s",
            (ujian["kelas_id"], pengguna["id"]), satu=True):
        return ujian
    abort(404)


def daftar_soal(ujian: dict, pengerjaan_id: int) -> list[dict]:
    from app import kueri
    soal = kueri("SELECT s.*, g.judul, g.lebar_px, g.tinggi_px, g.file_gambar, g.file_proxy "
                 "FROM soal s JOIN gambar g ON g.id = s.gambar_id "
                 "WHERE s.ujian_id = %s ORDER BY s.urutan, s.id", (ujian["id"],)) or []
    if ujian.get("acak") and pengerjaan_id:
        soal.sort(key=lambda s: hashlib.sha256(
            f"{pengerjaan_id}:{s['id']}".encode()).hexdigest())
    return soal


def sapu_kedaluwarsa() -> int:
    """Tutup dan nilai pengerjaan yang batasnya sudah lewat (tanpa cron)."""
    from app import kueri
    baris = kueri("SELECT id FROM pengerjaan WHERE status = 'jalan' "
                  "AND batas IS NOT NULL AND batas < NOW()") or []
    for r in baris:
        tutup_pengerjaan(r["id"])
    return len(baris)


def tutup_pengerjaan(pengerjaan_id: int, nilai_semua: bool = False) -> dict:
    """Tandai selesai lalu hitung nilai. `nilai_semua` menilai sisa jawaban isian."""
    from app import kueri
    if nilai_semua:
        kueri("UPDATE jawaban SET benar = 0 WHERE pengerjaan_id = %s AND benar IS NULL "
              "AND soal_id IN (SELECT id FROM soal WHERE tipe = 'isian_tunjuk')",
              (pengerjaan_id,))
    kueri("UPDATE pengerjaan SET status = 'selesai', selesai = COALESCE(selesai, NOW()) "
          "WHERE id = %s", (pengerjaan_id,))
    return penilaian.nilai_pengerjaan(pengerjaan_id)


def _pengerjaan(ujian_id: int, pengguna_id: int) -> dict | None:
    from app import kueri
    return kueri("SELECT * FROM pengerjaan WHERE ujian_id=%s AND pengguna_id=%s",
                 (ujian_id, pengguna_id), satu=True)


def _simpan_jawaban(pengerjaan_id: int, soal: dict, isian: str | None,
                    pilihan: int | None) -> dict:
    """Simpan/ubah satu jawaban; idempoten. Isian langsung dinilai."""
    from app import kueri
    if soal["tipe"] == "pilihan_ganda":
        teks = None
        benar = 1 if pilihan is not None and int(pilihan) == int(soal["jawaban_pg"] or -1) else 0
    else:
        teks = (isian or "").strip()[:500]
        sinonim = []
        if soal.get("sinonim"):
            try:
                sinonim = json.loads(soal["sinonim"])
            except (TypeError, ValueError):
                sinonim = [s.strip() for s in str(soal["sinonim"]).split(",") if s.strip()]
        hasil = penilaian.cocok(teks, soal.get("kunci_teks"), sinonim) if teks else None
        benar = None if hasil is None else (1 if hasil else 0)
        pilihan = None
    kueri("INSERT INTO jawaban (pengerjaan_id, soal_id, teks, pilihan, benar) "
          "VALUES (%s, %s, %s, %s, %s) "
          "ON DUPLICATE KEY UPDATE teks=VALUES(teks), pilihan=VALUES(pilihan), "
          "benar=VALUES(benar)",
          (pengerjaan_id, soal["id"], teks, pilihan, benar))
    return {"soal_id": soal["id"], "benar": benar}


# ── rute mahasiswa ─────────────────────────────────────────────────────────
@bp.get("/ujian/<int:ujian_id>")
@butuh("mahasiswa")
def halaman(ujian_id: int):
    saya = pengguna_sekarang()
    ujian = ujian_untuk(saya, ujian_id)
    if ujian["status"] == "draf":
        abort(404)
    sapu_kedaluwarsa()
    pengerjaan = _pengerjaan(ujian_id, saya["id"])
    if pengerjaan and pengerjaan["status"] != "jalan":
        return redirect(url_for("ujian.hasil", ujian_id=ujian_id))
    from app import kueri
    jumlah = kueri("SELECT COUNT(*) c FROM soal WHERE ujian_id=%s", (ujian_id,), satu=True)["c"]
    return render_template("ujian.html", ujian=ujian, pengerjaan=pengerjaan, jumlah=jumlah)


@bp.post("/ujian/<int:ujian_id>/mulai")
@butuh("mahasiswa")
def mulai(ujian_id: int):
    from app import kueri
    saya = pengguna_sekarang()
    ujian = ujian_untuk(saya, ujian_id)
    if ujian["status"] != "terbit":
        abort(404)
    sudah = _pengerjaan(ujian_id, saya["id"])
    if not sudah:
        batas = ujian.get("tutup")
        if ujian.get("durasi_menit"):
            kandidat = datetime.now() + timedelta(minutes=int(ujian["durasi_menit"]))
            batas = min(batas, kandidat) if batas else kandidat
        kueri("INSERT INTO pengerjaan (ujian_id, pengguna_id, mulai, batas, status) "
              "VALUES (%s, %s, NOW(), %s, 'jalan')", (ujian_id, saya["id"], batas))
    return redirect(url_for("ujian.soal", ujian_id=ujian_id, nomor=1))


@bp.route("/ujian/<int:ujian_id>/soal/<int:nomor>", methods=["GET", "POST"])
@butuh("mahasiswa")
def soal(ujian_id: int, nomor: int):
    saya = pengguna_sekarang()
    ujian = ujian_untuk(saya, ujian_id)
    pengerjaan = _pengerjaan(ujian_id, saya["id"])
    if not pengerjaan or pengerjaan["status"] != "jalan":
        return redirect(url_for("ujian.halaman", ujian_id=ujian_id))
    daftar = daftar_soal(ujian, pengerjaan["id"])
    if not daftar:
        return redirect(url_for("ujian.selesai", ujian_id=ujian_id))
    nomor = max(1, min(nomor, len(daftar)))
    kini = daftar[nomor - 1]

    if request.method == "POST":
        teks = request.form.get("teks")
        pilihan = request.form.get("pilihan")
        _simpan_jawaban(pengerjaan["id"], kini, teks, pilihan)
        if nomor < len(daftar):
            return redirect(url_for("ujian.soal", ujian_id=ujian_id, nomor=nomor + 1))
        return redirect(url_for("ujian.selesai", ujian_id=ujian_id))

    from app import kueri
    jawaban = kueri("SELECT * FROM jawaban WHERE pengerjaan_id=%s AND soal_id=%s",
                    (pengerjaan["id"], kini["id"]), satu=True)
    pilihan_teks = []
    if kini["tipe"] == "pilihan_ganda" and kini.get("pilihan"):
        try:
            pilihan_teks = json.loads(kini["pilihan"])
        except (TypeError, ValueError):
            pilihan_teks = []
    return render_template("ujian_soal.html", ujian=ujian, soal=kini, jawaban=jawaban,
                           nomor=nomor, jumlah=len(daftar), pilihan=pilihan_teks,
                           sisa=penilaian_sisa_waktu(pengerjaan))


def penilaian_sisa_waktu(pengerjaan: dict) -> int | None:
    batas = pengerjaan.get("batas")
    if not batas:
        return None
    return max(0, int((batas - datetime.now()).total_seconds()))


@bp.post("/api/jawaban")
@butuh("mahasiswa")
def api_jawaban():
    saya = pengguna_sekarang()
    data = request.get_json(silent=True) or request.form
    try:
        soal_id = int(data.get("soal_id"))
    except (TypeError, ValueError):
        return jsonify({"ok": False, "error": "soal_id tidak sah"}), 400
    from app import kueri
    baris = kueri("SELECT s.*, u.id ujian_id FROM soal s JOIN ujian u ON u.id = s.ujian_id "
                  "WHERE s.id = %s", (soal_id,), satu=True)
    if not baris:
        return jsonify({"ok": False, "error": "soal tidak ada"}), 404
    ujian_untuk(saya, baris["ujian_id"])
    pengerjaan = _pengerjaan(baris["ujian_id"], saya["id"])
    if not pengerjaan or pengerjaan["status"] != "jalan":
        return jsonify({"ok": False, "error": "ujian tidak sedang dikerjakan"}), 409
    hasil = _simpan_jawaban(pengerjaan["id"], baris, data.get("teks"), data.get("pilihan"))
    return jsonify({"ok": True, **hasil, "menunggu_tinjau": hasil["benar"] is None})


@bp.get("/ujian/<int:ujian_id>/selesai")
@butuh("mahasiswa")
def selesai(ujian_id: int):
    saya = pengguna_sekarang()
    ujian = ujian_untuk(saya, ujian_id)
    pengerjaan = _pengerjaan(ujian_id, saya["id"])
    if not pengerjaan:
        abort(404)
    if pengerjaan["status"] == "jalan":
        tutup_pengerjaan(pengerjaan["id"])
        flash("Jawaban dikumpulkan.", "ok")
    return redirect(url_for("ujian.hasil", ujian_id=ujian_id))


@bp.get("/ujian/<int:ujian_id>/hasil")
@butuh("mahasiswa")
def hasil(ujian_id: int):
    from app import kueri
    saya = pengguna_sekarang()
    ujian = ujian_untuk(saya, ujian_id)
    pengerjaan = _pengerjaan(ujian_id, saya["id"])
    if not pengerjaan or pengerjaan["status"] == "jalan":
        return redirect(url_for("ujian.halaman", ujian_id=ujian_id))
    ringkas = penilaian.nilai_pengerjaan(pengerjaan["id"])
    baris = kueri(
        "SELECT s.id, s.pertanyaan, s.x_pos, s.y_pos, s.skor, s.tipe, s.kunci_teks, "
        "s.pembahasan, j.teks, j.pilihan, j.benar "
        "FROM soal s LEFT JOIN jawaban j ON j.soal_id = s.id AND j.pengerjaan_id = %s "
        "WHERE s.ujian_id = %s ORDER BY s.urutan, s.id", (pengerjaan["id"], ujian_id)) or []
    tampil_kunci = bool(ujian["tampilkan_kunci"] and ujian["status"] == "tutup")
    return render_template("ujian_hasil.html", ujian=ujian, ringkas=ringkas, baris=baris,
                           tampil_kunci=tampil_kunci)
