"""Penilaian jawaban isian ujian.

Aturan (spec §5.3): jawaban dianggap benar bila sama setelah normalisasi, cocok
dengan salah satu sinonim, mirip secara ejaan (≥0,85), atau memuat kunci
multi-kata. Selain itu hasilnya None — artinya "menunggu tinjau dosen", bukan
salah otomatis, supaya mahasiswa tidak dihukum karena ejaan yang tidak lazim.
"""

from __future__ import annotations

import re
import unicodedata
from difflib import SequenceMatcher

AMBANG_MIRIP = 0.85
KATA_ABAIAKAN = {"di", "ke", "dari", "yang", "pada", "a", "the", "of", "dan", "atau"}


def normalisasi(teks: str | None) -> str:
    if not teks:
        return ""
    teks = unicodedata.normalize("NFKD", str(teks))
    teks = "".join(c for c in teks if not unicodedata.combining(c))
    teks = teks.lower()
    teks = re.sub(r"[^a-z0-9]+", " ", teks)
    return re.sub(r"\s+", " ", teks).strip()


def _inti(teks: str) -> str:
    kata = [k for k in teks.split() if k not in KATA_ABAIAKAN]
    return " ".join(kata) or teks


def _mirip(a: str, b: str) -> float:
    return SequenceMatcher(None, a, b).ratio()


def cocok(jawaban, kunci: str | None, sinonim: list[str] | None = None) -> bool | None:
    """True = benar, None = perlu ditinjau dosen (tidak pernah False otomatis)."""
    jawab = normalisasi(jawaban)
    kunci_bersih = normalisasi(kunci)
    if not jawab or not kunci_bersih:
        return None

    kandidat = {kunci_bersih}
    for s in sinonim or []:
        if normalisasi(s):
            kandidat.add(normalisasi(s))

    jawab_inti = _inti(jawab)
    for k in kandidat:
        if jawab == k or jawab_inti == _inti(k):
            return True
        if _mirip(jawab, k) >= AMBANG_MIRIP or _mirip(jawab_inti, _inti(k)) >= AMBANG_MIRIP:
            return True
        # kunci multi-kata muncul utuh di dalam jawaban ("lapisan tunica mucosa usus")
        if len(k.split()) >= 2 and k in jawab:
            return True
    return None


def nilai_pengerjaan(pengerjaan_id: int) -> dict:
    """Hitung nilai dari jawaban yang sudah dinilai; sisakan yang menunggu tinjau."""
    from flask import has_app_context
    from app import app as flask_app, kueri

    def hitung() -> dict:
        baris = kueri(
            "SELECT s.skor, j.benar, j.id jid FROM soal s "
            "JOIN ujian u ON u.id = s.ujian_id "
            "JOIN pengerjaan p ON p.ujian_id = u.id AND p.id = %s "
            "LEFT JOIN jawaban j ON j.soal_id = s.id AND j.pengerjaan_id = p.id",
            (pengerjaan_id,)) or []
        total = sum((r["skor"] or 0) for r in baris)
        benar = sum((r["skor"] or 0) for r in baris if r["benar"] == 1)
        # yang menunggu tinjau = sudah dijawab tetapi belum dinilai; soal kosong
        # dihitung salah (0), bukan "menunggu".
        menunggu = sum(1 for r in baris if r["jid"] and r["benar"] is None)
        hasil = {"total_skor": total, "skor_benar": benar, "menunggu": menunggu,
                 "nilai": round(100 * benar / total) if total else 0}
        kueri("UPDATE pengerjaan SET nilai=%s WHERE id=%s",
              (hasil["nilai"], pengerjaan_id))
        return hasil

    if has_app_context():
        return hitung()
    with flask_app.app_context():
        return hitung()
