# retoor <retoor@molodetz.nl>

from datetime import datetime, timezone

import pytest
from pydantic import ValidationError

from devplacepy.config import QUIZ_MAX_OPTIONS, QUIZ_MAX_QUESTIONS
from devplacepy.database import get_table
from devplacepy.models import QuizDocument, QuizImportForm
from devplacepy.services.quiz import store
from devplacepy.services.quiz.store import QuizError
from devplacepy.utils import generate_uid, make_combined_slug

_counter = [0]

DOCUMENT = {
    "title": "SQLite fundamentals",
    "description": "Questions on WAL and indexing.",
    "settings": {"shuffle_questions": True, "reveal_answers": True, "pass_percent": 70},
    "questions": [
        {
            "kind": "single_choice",
            "prompt": "Which journal mode allows concurrent readers?",
            "points": 2,
            "explanation": "WAL keeps readers off the writer's lock.",
            "options": [{"label": "DELETE"}, {"label": "WAL", "is_correct": True}],
        },
        {
            "kind": "free_text",
            "prompt": "Why does a partial index help?",
            "points": 3,
            "expected_answer": "It keeps the live query off a one bucket index.",
            "grading_criteria": "Accept any answer mentioning the planner.",
        },
    ],
}


def _user(prefix):
    _counter[0] += 1
    uid = generate_uid()
    get_table("users").insert(
        {
            "uid": uid,
            "username": f"{prefix}{_counter[0]}",
            "terms_version": "1",
            "email": f"{prefix}{_counter[0]}@t.dev",
            "role": "Member",
            "api_key": generate_uid(),
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    return get_table("users").find_one(uid=uid)


def _quiz(owner, document):
    uid = generate_uid()
    get_table("quizzes").insert(
        store.born_live(
            {
                "uid": uid,
                "user_uid": owner["uid"],
                "slug": make_combined_slug(document.title, uid),
                "title": document.title,
                "description": document.description,
                "status": "draft",
                "published_at": "",
                "question_count": 0,
                "total_points": 0,
                "attempt_count": 0,
                "stars": 0,
                **store.document_settings(document),
            }
        )
    )
    store.import_questions(uid, document)
    return store.get_quiz(uid)


def test_parse_document_accepts_the_reference_shape():
    document = store.parse_document(DOCUMENT)
    assert document.title == "SQLite fundamentals"
    assert len(document.questions) == 2


def test_parse_document_rejects_junk():
    with pytest.raises(QuizError):
        store.parse_document({"title": "x"})


def test_the_import_form_parses_a_json_string():
    import json

    form = QuizImportForm(document=json.dumps(DOCUMENT))
    assert form.document.title == "SQLite fundamentals"


def test_the_import_form_rejects_invalid_json():
    with pytest.raises(ValidationError):
        QuizImportForm(document="{not json")


def test_a_document_needs_at_least_one_question():
    with pytest.raises(ValidationError):
        QuizDocument(title="Empty quiz", questions=[])


def test_a_document_is_capped_at_the_question_limit():
    question = {"kind": "numeric", "prompt": "q", "numeric_value": 1}
    with pytest.raises(ValidationError):
        QuizDocument(title="Too big", questions=[question] * (QUIZ_MAX_QUESTIONS + 1))


def test_a_question_is_capped_at_the_option_limit():
    options = [{"label": f"L{index}"} for index in range(QUIZ_MAX_OPTIONS + 1)]
    options[0]["is_correct"] = True
    with pytest.raises(ValidationError):
        QuizDocument(
            title="Too many options",
            questions=[{"kind": "single_choice", "prompt": "q", "options": options}],
        )


def test_a_single_choice_needs_exactly_one_correct_option():
    with pytest.raises(ValidationError):
        QuizDocument(
            title="Bad choice",
            questions=[
                {
                    "kind": "single_choice",
                    "prompt": "q",
                    "options": [{"label": "a"}, {"label": "b"}],
                }
            ],
        )


def test_a_free_text_needs_a_reference_or_criteria():
    with pytest.raises(ValidationError):
        QuizDocument(
            title="Bad free text",
            questions=[{"kind": "free_text", "prompt": "explain"}],
        )


def test_a_matching_needs_a_right_hand_value_on_every_pair():
    with pytest.raises(ValidationError):
        QuizDocument(
            title="Bad matching",
            questions=[
                {
                    "kind": "matching",
                    "prompt": "q",
                    "options": [{"label": "a", "match_value": "A"}, {"label": "b"}],
                }
            ],
        )


def test_document_settings_map_onto_the_quiz_columns():
    settings = store.document_settings(store.parse_document(DOCUMENT))
    assert settings["shuffle_questions"] == 1
    assert settings["reveal_answers"] == 1
    assert settings["pass_percent"] == 70


def test_importing_creates_every_question_in_order(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    questions = store.list_questions(quiz["uid"])
    assert [q["kind"] for q in questions] == ["single_choice", "free_text"]
    assert [q["position"] for q in questions] == [0, 1]


def test_importing_recomputes_the_totals(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    assert quiz["question_count"] == 2
    assert quiz["total_points"] == 5


def test_importing_stores_the_kind_specific_fields(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    free_text = store.list_questions(quiz["uid"])[1]
    assert free_text["expected_answer"].startswith("It keeps")
    assert free_text["grading_criteria"].startswith("Accept")


def test_the_owner_export_round_trips_through_import(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    exported = store.export_document(quiz["uid"], True)
    reimported = _quiz(owner, store.parse_document(exported))
    assert reimported["question_count"] == quiz["question_count"]
    assert reimported["total_points"] == quiz["total_points"]
    assert store.export_document(reimported["uid"], True) == exported


def test_the_owner_export_carries_the_answer_key(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    exported = store.export_document(quiz["uid"], True)
    assert any(option.get("is_correct") for option in exported["questions"][0]["options"])
    assert exported["questions"][1]["expected_answer"]


def test_the_public_export_omits_the_answer_key(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    exported = store.export_document(quiz["uid"], False)
    assert all("is_correct" not in option for option in exported["questions"][0]["options"])
    assert "expected_answer" not in exported["questions"][1]
    assert "correct_boolean" not in exported["questions"][0]


def test_the_public_export_keeps_the_prompts_and_labels(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    exported = store.export_document(quiz["uid"], False)
    assert exported["questions"][0]["prompt"].startswith("Which journal mode")
    assert [option["label"] for option in exported["questions"][0]["options"]] == ["DELETE", "WAL"]


def test_the_export_carries_the_settings(local_db):
    owner = _user("qd")
    quiz = _quiz(owner, store.parse_document(DOCUMENT))
    exported = store.export_document(quiz["uid"], False)
    assert exported["settings"]["pass_percent"] == 70
    assert exported["settings"]["reveal_answers"] is True


def test_exporting_an_unknown_quiz_raises(local_db):
    with pytest.raises(QuizError):
        store.export_document("does-not-exist", True)
