# retoor <retoor@molodetz.nl>

from typing import Annotated

from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, JSONResponse

from devplacepy.config import QUIZ_ANSWER_MAX_CHARS
from devplacepy.database import get_users_by_uids
from devplacepy.dependencies import json_or_form
from devplacepy.models import QuizAnswerForm
from devplacepy.responses import action_result, respond, wants_json
from devplacepy.schemas import (
    QuizAnswerResultOut,
    QuizAttemptPageOut,
    QuizResultOut,
)
from devplacepy.services.audit import record as audit
from devplacepy.services.quiz import QuizError, store
from devplacepy.utils import (
    award_rewards,
    create_notification,
    is_admin,
    not_found,
    require_user,
    track_action,
    XP_QUIZ_COMPLETE,
)

from ._shared import action_error, load_quiz, notify_quiz, quiz_seo, quiz_url

router = APIRouter()


def _load_attempt(quiz: dict, attempt_uid: str, user: dict, allow_admin: bool = False):
    attempt = store.get_attempt(attempt_uid)
    if not attempt or attempt.get("quiz_uid") != quiz["uid"]:
        raise not_found("Attempt not found")
    if attempt.get("user_uid") != user["uid"] and not (allow_admin and is_admin(user)):
        raise not_found("Attempt not found")
    return attempt


def _author(quiz: dict):
    return get_users_by_uids([quiz["user_uid"]]).get(quiz["user_uid"])


@router.post("/{slug}/attempts")
async def start_attempt(request: Request, slug: str):
    user = require_user(request)
    quiz = load_quiz(slug, user)
    if quiz.get("status") != "published":
        return action_error(request, "This quiz is not published yet", quiz_url(quiz))
    try:
        attempt = store.start_attempt(user, quiz)
    except QuizError as exc:
        return action_error(request, str(exc), quiz_url(quiz))
    audit.record(
        request,
        "quiz.attempt.start",
        user=user,
        target_type="quiz",
        target_uid=quiz["uid"],
        target_label=quiz.get("title") or quiz["uid"],
        summary=f"{user['username']} started an attempt on quiz {quiz.get('title')}",
        metadata={"attempt_uid": attempt["uid"]},
        links=[audit.target("quiz", quiz["uid"], quiz.get("title") or "")],
    )
    url = f"{quiz_url(quiz)}/attempts/{attempt['uid']}"
    return action_result(
        request, url, data={"uid": attempt["uid"], "url": url, "status": attempt["status"]}
    )


@router.get("/{slug}/attempts/{attempt_uid}", response_class=HTMLResponse)
async def attempt_page(request: Request, slug: str, attempt_uid: str):
    user = require_user(request)
    quiz = load_quiz(slug, user)
    attempt = _load_attempt(quiz, attempt_uid, user)
    payload = store.serialize_attempt(quiz, attempt, user)
    seo_ctx = quiz_seo(
        request,
        quiz.get("title") or "Quiz",
        "Play this quiz on DevPlace.",
        robots="noindex,follow",
        breadcrumbs=[{"name": quiz.get("title") or "Quiz", "url": quiz_url(quiz)}],
    )
    return respond(
        request,
        "quiz_play.html",
        {
            **seo_ctx,
            "request": request,
            "user": user,
            "quiz": store.serialize_quiz(quiz, user, _author(quiz)),
            "attempt": payload,
            "answer_max_chars": QUIZ_ANSWER_MAX_CHARS,
            "quiz_error": request.query_params.get("error", ""),
        },
        model=QuizAttemptPageOut,
    )


@router.post("/{slug}/attempts/{attempt_uid}/answer")
async def submit_answer(
    request: Request,
    slug: str,
    attempt_uid: str,
    data: Annotated[QuizAnswerForm, Depends(json_or_form(QuizAnswerForm))],
):
    user = require_user(request)
    quiz = load_quiz(slug, user)
    attempt = _load_attempt(quiz, attempt_uid, user)
    attempt_url = f"{quiz_url(quiz)}/attempts/{attempt_uid}"
    try:
        answer, updated, result = await store.answer(
            user,
            quiz,
            attempt,
            data.question_uid,
            data.submission(),
        )
    except QuizError as exc:
        return action_error(request, str(exc), attempt_url)
    if result.graded_by == "fallback":
        audit.record_system(
            "quiz.grade.failed",
            result="failure",
            target_type="quiz",
            target_uid=quiz["uid"],
            summary=(
                f"AI grading unavailable for question {data.question_uid}, "
                "deterministic fallback used"
            ),
            metadata={"attempt_uid": attempt_uid, "question_uid": data.question_uid},
        )
    audit.record(
        request,
        "quiz.attempt.answer",
        user=user,
        target_type="quiz",
        target_uid=quiz["uid"],
        target_label=quiz.get("title") or quiz["uid"],
        summary=(
            f"{user['username']} answered a question on quiz {quiz.get('title')} "
            f"for {answer.get('awarded_points')} points"
        ),
        metadata={
            "attempt_uid": attempt_uid,
            "question_uid": data.question_uid,
            "graded_by": result.graded_by,
        },
        links=[audit.target("quiz", quiz["uid"], quiz.get("title") or "")],
    )
    if not wants_json(request):
        return action_result(request, attempt_url)
    return JSONResponse(
        QuizAnswerResultOut(
            answer=store.serialize_answer(answer),
            attempt=store.serialize_attempt(quiz, updated, user),
        ).model_dump(mode="json")
    )


@router.post("/{slug}/attempts/{attempt_uid}/finish")
async def finish_attempt(request: Request, slug: str, attempt_uid: str):
    user = require_user(request)
    quiz = load_quiz(slug, user)
    attempt = _load_attempt(quiz, attempt_uid, user)
    finished, won = store.finish(quiz, attempt)
    results_url = f"{quiz_url(quiz)}/attempts/{attempt_uid}/results"
    if won:
        store.clear_cache()
        award_rewards(user["uid"], XP_QUIZ_COMPLETE, "Quiz Taker")
        track_action(user["uid"], "quiz_complete")
        if float(finished.get("score_percent") or 0.0) >= 100.0:
            track_action(user["uid"], "quiz_perfect")
        if quiz["user_uid"] != user["uid"]:
            create_notification(
                quiz["user_uid"],
                "quiz_attempt",
                f"{user['username']} completed your quiz {quiz.get('title')}",
                user["uid"],
                quiz_url(quiz),
            )
        audit.record(
            request,
            "quiz.attempt.finish",
            user=user,
            target_type="quiz",
            target_uid=quiz["uid"],
            target_label=quiz.get("title") or quiz["uid"],
            summary=(
                f"{user['username']} finished quiz {quiz.get('title')} with "
                f"{finished.get('score_percent')}%"
            ),
            metadata={
                "attempt_uid": attempt_uid,
                "score_percent": finished.get("score_percent"),
                "passed": finished.get("passed"),
            },
            links=[audit.target("quiz", quiz["uid"], quiz.get("title") or "")],
        )
        await notify_quiz(quiz["uid"])
    if not wants_json(request):
        return action_result(request, results_url)
    result = store.serialize_result(quiz, finished, user)
    return JSONResponse(
        QuizResultOut(
            quiz=store.serialize_quiz(quiz, user, _author(quiz)),
            attempt=result,
            review=result["review"],
            fallback_count=result["fallback_count"],
        ).model_dump(mode="json")
    )


@router.get("/{slug}/attempts/{attempt_uid}/results", response_class=HTMLResponse)
async def attempt_results(request: Request, slug: str, attempt_uid: str):
    user = require_user(request)
    quiz = load_quiz(slug, user)
    attempt = _load_attempt(quiz, attempt_uid, user, allow_admin=True)
    result = store.serialize_result(quiz, attempt, user)
    seo_ctx = quiz_seo(
        request,
        f"{quiz.get('title') or 'Quiz'} results",
        "Your quiz result on DevPlace.",
        robots="noindex,follow",
        breadcrumbs=[{"name": quiz.get("title") or "Quiz", "url": quiz_url(quiz)}],
    )
    return respond(
        request,
        "quiz_results.html",
        {
            **seo_ctx,
            "request": request,
            "user": user,
            "quiz": store.serialize_quiz(quiz, user, _author(quiz)),
            "attempt": result,
            "review": result["review"],
            "fallback_count": result["fallback_count"],
        },
        model=QuizResultOut,
    )
