# retoor <retoor@molodetz.nl>

from __future__ import annotations

from devplacepy.database import _now_iso, soft_delete, soft_delete_in
from devplacepy.utils import generate_uid

from .. import scoring
from .common import (
    QuizError,
    _options,
    _questions,
    born_live,
    guard_editable,
)

KIND_FIELDS = (
    "correct_boolean",
    "expected_answer",
    "grading_criteria",
    "numeric_value",
    "numeric_tolerance",
    "case_sensitive",
)


def list_questions(quiz_uid: str) -> list[dict]:
    return list(
        _questions().find(quiz_uid=quiz_uid, deleted_at=None, order_by=["position", "id"])
    )


def get_question(quiz_uid: str, question_uid: str) -> dict | None:
    if not question_uid:
        return None
    return _questions().find_one(
        uid=question_uid, quiz_uid=quiz_uid, deleted_at=None
    )


def list_options(question_uid: str) -> list[dict]:
    return list(
        _options().find(
            question_uid=question_uid, deleted_at=None, order_by=["position", "id"]
        )
    )


def options_by_question(quiz_uid: str) -> dict[str, list[dict]]:
    grouped: dict[str, list[dict]] = {}
    for option in _options().find(
        quiz_uid=quiz_uid, deleted_at=None, order_by=["position", "id"]
    ):
        grouped.setdefault(option["question_uid"], []).append(option)
    return grouped


def add_question(quiz_uid: str, payload: dict) -> dict:
    guard_editable(quiz_uid)
    kind = payload.get("kind", "")
    if kind not in scoring.KINDS_BY_KEY:
        raise QuizError("Unknown question type")
    position = len(list_questions(quiz_uid))
    uid = generate_uid()
    row = born_live(
        {
            "uid": uid,
            "quiz_uid": quiz_uid,
            "position": position,
            "kind": kind,
            "prompt": payload.get("prompt", "") or "",
            "explanation": payload.get("explanation", "") or "",
            "points": max(1, min(100, int(payload.get("points") or 1))),
            "media_attachment_uid": payload.get("media_attachment_uid", "") or "",
            **{field: payload.get(field) for field in KIND_FIELDS},
        }
    )
    row["correct_boolean"] = int(row.get("correct_boolean") or 0)
    row["expected_answer"] = row.get("expected_answer") or ""
    row["grading_criteria"] = row.get("grading_criteria") or ""
    row["numeric_value"] = float(row.get("numeric_value") or 0.0)
    row["numeric_tolerance"] = float(row.get("numeric_tolerance") or 0.0)
    row["case_sensitive"] = int(row.get("case_sensitive") or 0)
    _questions().insert(row)
    _write_options(quiz_uid, uid, payload.get("options") or [])
    from .quizzes import recompute_quiz_totals

    recompute_quiz_totals(quiz_uid)
    return row


def edit_question(quiz_uid: str, question_uid: str, payload: dict) -> dict:
    guard_editable(quiz_uid)
    question = get_question(quiz_uid, question_uid)
    if not question:
        raise QuizError("Question not found")
    kind = payload.get("kind") or question["kind"]
    if kind not in scoring.KINDS_BY_KEY:
        raise QuizError("Unknown question type")
    update = {
        "uid": question_uid,
        "kind": kind,
        "prompt": payload.get("prompt", "") or "",
        "explanation": payload.get("explanation", "") or "",
        "points": max(1, min(100, int(payload.get("points") or 1))),
        "media_attachment_uid": payload.get("media_attachment_uid", "") or "",
        "correct_boolean": int(payload.get("correct_boolean") or 0),
        "expected_answer": payload.get("expected_answer") or "",
        "grading_criteria": payload.get("grading_criteria") or "",
        "numeric_value": float(payload.get("numeric_value") or 0.0),
        "numeric_tolerance": float(payload.get("numeric_tolerance") or 0.0),
        "case_sensitive": int(payload.get("case_sensitive") or 0),
        "updated_at": _now_iso(),
    }
    _questions().update(update, ["uid"])
    soft_delete("quiz_options", "system", question_uid=question_uid)
    _write_options(quiz_uid, question_uid, payload.get("options") or [])
    from .quizzes import recompute_quiz_totals

    recompute_quiz_totals(quiz_uid)
    return {**question, **update}


def delete_question(quiz_uid: str, question_uid: str, deleted_by: str) -> dict:
    guard_editable(quiz_uid)
    question = get_question(quiz_uid, question_uid)
    if not question:
        raise QuizError("Question not found")
    stamp = _now_iso()
    soft_delete("quiz_options", deleted_by, stamp=stamp, question_uid=question_uid)
    soft_delete("quiz_questions", deleted_by, stamp=stamp, uid=question_uid)
    _renumber(quiz_uid)
    from .quizzes import recompute_quiz_totals

    recompute_quiz_totals(quiz_uid)
    return question


def reorder_questions(quiz_uid: str, order: list[str]) -> list[dict]:
    guard_editable(quiz_uid)
    existing = {question["uid"]: question for question in list_questions(quiz_uid)}
    wanted = [uid for uid in order if uid in existing]
    if len(wanted) != len(existing):
        raise QuizError("The new order must list every question exactly once")
    stamp = _now_iso()
    for position, uid in enumerate(wanted):
        _questions().update(
            {"uid": uid, "position": position, "updated_at": stamp}, ["uid"]
        )
    return list_questions(quiz_uid)


def _renumber(quiz_uid: str) -> None:
    stamp = _now_iso()
    for position, question in enumerate(list_questions(quiz_uid)):
        if int(question.get("position") or 0) != position:
            _questions().update(
                {"uid": question["uid"], "position": position, "updated_at": stamp},
                ["uid"],
            )


def _write_options(quiz_uid: str, question_uid: str, options: list[dict]) -> None:
    for position, option in enumerate(options):
        _options().insert(
            born_live(
                {
                    "uid": generate_uid(),
                    "question_uid": question_uid,
                    "quiz_uid": quiz_uid,
                    "position": position,
                    "label": (option.get("label") or "").strip(),
                    "match_value": (option.get("match_value") or "").strip(),
                    "is_correct": int(bool(option.get("is_correct"))),
                }
            )
        )


def cascade_questions(quiz_uid: str, deleted_by: str, stamp: str) -> None:
    for table in ("quiz_questions", "quiz_options", "quiz_attempts", "quiz_answers"):
        soft_delete_in(table, "quiz_uid", [quiz_uid], deleted_by, stamp=stamp)
