# retoor <retoor@molodetz.nl>

from __future__ import annotations

from devplacepy.utils import is_admin

from .. import scoring
from .attempts import (
    answers_for,
    attempt_order,
    expire_if_due,
)
from .common import _now, is_quiz_owner, load_json
from .questions import list_questions, options_by_question
from .quizzes import validation_errors

ANSWER_KEY_QUESTION_FIELDS = (
    "correct_boolean",
    "expected_answer",
    "grading_criteria",
    "numeric_value",
    "numeric_tolerance",
)


def serialize_option(option: dict, *, reveal: bool) -> dict:
    payload = {
        "uid": option["uid"],
        "position": int(option.get("position") or 0),
        "label": option.get("label") or "",
    }
    if reveal:
        payload["is_correct"] = bool(int(option.get("is_correct") or 0))
        payload["match_value"] = option.get("match_value") or ""
    return payload


def serialize_question(
    question: dict,
    options: list[dict],
    *,
    reveal: bool,
    shuffle_options: bool = False,
    seed: str = "",
    answer: dict | None = None,
) -> dict:
    ordered = sorted(options, key=lambda option: int(option.get("position") or 0))
    if shuffle_options and question.get("kind") != "ordering":
        order = scoring.option_order([o["uid"] for o in ordered], True, seed)
        by_uid = {o["uid"]: o for o in ordered}
        ordered = [by_uid[uid] for uid in order]
    payload = {
        "uid": question["uid"],
        "quiz_uid": question.get("quiz_uid") or "",
        "position": int(question.get("position") or 0),
        "kind": question.get("kind") or "",
        "kind_label": scoring.kind_label(question.get("kind") or ""),
        "prompt": question.get("prompt") or "",
        "explanation": question.get("explanation") or "" if reveal else "",
        "points": max(1, int(question.get("points") or 1)),
        "media_attachment_uid": question.get("media_attachment_uid") or "",
        "case_sensitive": bool(int(question.get("case_sensitive") or 0)),
        "options": [serialize_option(option, reveal=reveal) for option in ordered],
        "option_count": len(ordered),
        "match_choices": _match_choices(question, options, seed),
    }
    if reveal:
        payload["correct_boolean"] = bool(int(question.get("correct_boolean") or 0))
        payload["expected_answer"] = question.get("expected_answer") or ""
        payload["grading_criteria"] = question.get("grading_criteria") or ""
        payload["numeric_value"] = float(question.get("numeric_value") or 0.0)
        payload["numeric_tolerance"] = float(question.get("numeric_tolerance") or 0.0)
    if answer is not None:
        payload["answer"] = serialize_answer(answer)
    return payload


def _match_choices(question: dict, options: list[dict], seed: str) -> list[str]:
    if question.get("kind") != "matching":
        return []
    values = sorted(
        {(option.get("match_value") or "").strip() for option in options} - {""}
    )
    order = scoring.option_order(values, True, seed or question["uid"])
    return order


def serialize_answer(answer: dict) -> dict:
    return {
        "uid": answer["uid"],
        "question_uid": answer.get("question_uid") or "",
        "position": int(answer.get("position") or 0),
        "answer_text": answer.get("answer_text") or "",
        "option_uids": load_json(answer.get("option_uids"), []),
        "answered": bool(answer.get("answered_at")),
        "answered_at": answer.get("answered_at") or "",
        "is_correct": bool(int(answer.get("is_correct") or 0)),
        "awarded_points": round(float(answer.get("awarded_points") or 0.0), 2),
        "feedback": answer.get("feedback") or "",
        "graded_by": answer.get("graded_by") or "",
        "confidence": round(float(answer.get("confidence") or 0.0), 2),
    }


def serialize_quiz(quiz: dict, viewer: dict | None, author: dict | None = None) -> dict:
    owns = is_quiz_owner(quiz, viewer)
    status = quiz.get("status") or "draft"
    draft = status == "draft"
    return {
        "uid": quiz["uid"],
        "slug": quiz.get("slug") or quiz["uid"],
        "url": f"/quizzes/{quiz.get('slug') or quiz['uid']}",
        "title": quiz.get("title") or "",
        "description": quiz.get("description") or "",
        "status": status,
        "published_at": quiz.get("published_at") or "",
        "shuffle_questions": bool(int(quiz.get("shuffle_questions") or 0)),
        "shuffle_options": bool(int(quiz.get("shuffle_options") or 0)),
        "reveal_answers": bool(int(quiz.get("reveal_answers") or 0)),
        "allow_review": bool(int(quiz.get("allow_review") or 0)),
        "time_limit_seconds": int(quiz.get("time_limit_seconds") or 0),
        "pass_percent": int(quiz.get("pass_percent") or 0),
        "question_count": int(quiz.get("question_count") or 0),
        "total_points": int(quiz.get("total_points") or 0),
        "attempt_count": int(quiz.get("attempt_count") or 0),
        "stars": int(quiz.get("stars") or 0),
        "created_at": quiz.get("created_at") or "",
        "author": author,
        "viewer_owns": owns,
        "viewer_is_admin": is_admin(viewer),
        "viewer_can_edit": owns and draft,
        "viewer_can_play": bool(viewer) and not draft,
        "validation_errors": validation_errors(quiz["uid"]) if owns and draft else [],
    }


def serialize_builder(quiz: dict, viewer: dict | None) -> list[dict]:
    reveal = is_quiz_owner(quiz, viewer) or is_admin(viewer)
    grouped = options_by_question(quiz["uid"])
    return [
        serialize_question(
            question, grouped.get(question["uid"], []), reveal=reveal
        )
        for question in list_questions(quiz["uid"])
    ]


def serialize_attempt(quiz: dict, attempt: dict, viewer: dict | None) -> dict:
    attempt = expire_if_due(attempt)
    owns_quiz = is_quiz_owner(quiz, viewer)
    reveal_answered = bool(int(quiz.get("reveal_answers") or 0))
    grouped = options_by_question(quiz["uid"])
    questions = {question["uid"]: question for question in list_questions(quiz["uid"])}
    answers = {answer["question_uid"]: answer for answer in answers_for(attempt["uid"])}
    payload_questions = []
    for position, question_uid in enumerate(attempt_order(attempt)):
        question = questions.get(question_uid)
        if not question:
            continue
        answer = answers.get(question_uid)
        answered = bool(answer and answer.get("answered_at"))
        reveal = owns_quiz or (answered and reveal_answered)
        payload_questions.append(
            {
                **serialize_question(
                    question,
                    grouped.get(question_uid, []),
                    reveal=reveal,
                    shuffle_options=bool(int(quiz.get("shuffle_options") or 0)),
                    seed=f"{attempt['uid']}:{question_uid}",
                    answer=answer,
                ),
                "position": position,
            }
        )
    total = round(float(attempt.get("score_points") or 0.0), 2)
    max_points = int(attempt.get("max_points") or 0)
    percent = scoring.score_percent(total, max_points)
    return {
        "uid": attempt["uid"],
        "quiz_uid": quiz["uid"],
        "quiz_slug": quiz.get("slug") or quiz["uid"],
        "quiz_title": quiz.get("title") or "",
        "user_uid": attempt.get("user_uid") or "",
        "status": attempt.get("status") or "",
        "started_at": attempt.get("started_at") or "",
        "expires_at": attempt.get("expires_at") or "",
        "completed_at": attempt.get("completed_at") or "",
        "remaining_seconds": scoring.remaining_seconds(
            attempt.get("expires_at") or "", _now()
        ),
        "answered_count": int(attempt.get("answered_count") or 0),
        "question_count": len(payload_questions),
        "score_points": total,
        "max_points": max_points,
        "score_percent": percent,
        "passed": bool(int(attempt.get("passed") or 0)),
        "pass_percent": int(quiz.get("pass_percent") or 0),
        "allow_review": bool(int(quiz.get("allow_review") or 0)),
        "questions": payload_questions,
    }


def serialize_result(quiz: dict, attempt: dict, viewer: dict | None) -> dict:
    payload = serialize_attempt(quiz, attempt, viewer)
    review = payload["questions"] if payload["allow_review"] else []
    return {
        **payload,
        "review": review,
        "fallback_count": sum(
            1
            for question in payload["questions"]
            if (question.get("answer") or {}).get("graded_by") == "fallback"
        ),
    }
