# retoor <retoor@molodetz.nl>

from datetime import datetime, timedelta, timezone

import requests

from tests.api.quizzes._helpers import (
    JSON,
    NUMERIC,
    answer_correctly,
    attempt_state,
    build_published,
    create_quiz,
    signup,
    start_attempt,
)
from tests.conftest import BASE_URL


def _play(session, slug, attempt_uid):
    state = attempt_state(session, slug, attempt_uid)
    for question in state["attempt"]["questions"]:
        answer_correctly(session, slug, attempt_uid, question)
    return session.post(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/finish", headers=JSON
    )


def test_starting_requires_auth(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Auth attempt quiz")
    response = requests.post(f"{BASE_URL}/quizzes/{slug}/attempts", headers=JSON)
    assert response.status_code in (401, 303)


def test_a_draft_cannot_be_played(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session, "Unplayable draft")
    response = session.post(f"{BASE_URL}/quizzes/{slug}/attempts", headers=JSON)
    assert response.status_code == 400


def test_starting_returns_an_in_progress_attempt(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Start quiz")
    player, _ = signup()
    response = player.post(f"{BASE_URL}/quizzes/{slug}/attempts", headers=JSON)
    assert response.status_code == 200
    assert response.json()["data"]["status"] == "in_progress"


def test_starting_twice_resumes_the_same_attempt(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Resume quiz")
    player, _ = signup()
    first = start_attempt(player, slug)
    second = start_attempt(player, slug)
    assert first == second


def test_the_attempt_page_lists_every_question(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Question list quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    payload = attempt_state(player, slug, attempt_uid)
    assert payload["attempt"]["question_count"] == 2
    assert len(payload["attempt"]["questions"]) == 2


def test_the_attempt_snapshots_the_max_points(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Max points quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    assert attempt_state(player, slug, attempt_uid)["attempt"]["max_points"] == 3


def test_another_member_cannot_read_the_attempt(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Private attempt quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    other, _ = signup()
    response = other.get(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}", headers=JSON)
    assert response.status_code == 404


def test_the_quiz_author_cannot_read_a_members_attempt(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Author blind quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    response = owner.get(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}", headers=JSON)
    assert response.status_code == 404


def test_answering_grades_and_credits(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Grading quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    payload = answer_correctly(player, slug, attempt_uid, question).json()
    assert payload["answer"]["answered"] is True
    assert payload["answer"]["is_correct"] is True
    assert payload["answer"]["graded_by"] == "auto"
    assert payload["attempt"]["answered_count"] == 1


def test_a_wrong_answer_scores_zero(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Wrong answer quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    payload = player.post(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/answer",
        data={"question_uid": question["uid"], "answer_text": "7"},
        headers=JSON,
    ).json()
    assert payload["answer"]["is_correct"] is False
    assert payload["answer"]["awarded_points"] == 0.0


def test_a_double_submit_is_refused(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Double submit quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    answer_correctly(player, slug, attempt_uid, question)
    second = answer_correctly(player, slug, attempt_uid, question)
    assert second.status_code == 400
    assert "already answered" in second.json()["error"]["message"]


def test_a_double_submit_credits_nothing(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="No double credit quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    first = answer_correctly(player, slug, attempt_uid, question).json()
    answer_correctly(player, slug, attempt_uid, question)
    after = attempt_state(player, slug, attempt_uid)["attempt"]
    assert after["answered_count"] == first["attempt"]["answered_count"]
    assert after["score_points"] == first["attempt"]["score_points"]


def test_answering_an_unknown_question_is_a_400(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Unknown question quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    response = player.post(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/answer",
        data={"question_uid": "nope", "answer_text": "x"},
        headers=JSON,
    )
    assert response.status_code == 400


def test_an_oversized_answer_is_rejected_before_grading(app_server, seeded_db):
    from devplacepy.config import QUIZ_ANSWER_MAX_CHARS

    owner, _ = signup()
    slug = build_published(owner, title="Oversized answer quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    response = player.post(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/answer",
        data={"question_uid": question["uid"], "answer_text": "x" * (QUIZ_ANSWER_MAX_CHARS + 1)},
        headers=JSON,
    )
    assert response.status_code == 422


def test_finishing_returns_the_result(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Finish quiz", pass_percent="50")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    payload = _play(player, slug, attempt_uid).json()
    assert payload["attempt"]["status"] == "completed"
    assert payload["attempt"]["score_percent"] == 100.0
    assert payload["attempt"]["passed"] is True


def test_finishing_twice_returns_the_same_result(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Finish twice quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    first = _play(player, slug, attempt_uid).json()
    second = player.post(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/finish", headers=JSON
    ).json()
    assert second["attempt"]["score_points"] == first["attempt"]["score_points"]
    assert second["attempt"]["status"] == "completed"


def test_finishing_twice_increments_the_attempt_count_once(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Attempt count quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    player.post(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/finish", headers=JSON)
    payload = requests.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).json()
    assert payload["quiz"]["attempt_count"] == 1


def test_finishing_notifies_the_author_once(app_server, seeded_db):
    from devplacepy.database import get_table, refresh_snapshot

    owner, owner_name = signup()
    slug = build_published(owner, title="Notify quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    player.post(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/finish", headers=JSON)
    refresh_snapshot()
    author = get_table("users").find_one(username=owner_name)
    count = get_table("notifications").count(user_uid=author["uid"], type="quiz_attempt")
    assert count == 1


def test_playing_again_creates_a_second_attempt(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Replay quiz")
    player, _ = signup()
    first = start_attempt(player, slug)
    _play(player, slug, first)
    second = start_attempt(player, slug)
    assert second != first


def test_an_expired_attempt_refuses_answers(app_server, seeded_db):
    from devplacepy.database import get_table, refresh_snapshot

    owner, _ = signup()
    slug = build_published(owner, title="Expiring quiz", time_limit_seconds="3600")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    refresh_snapshot()
    past = (datetime.now(timezone.utc) - timedelta(seconds=5)).isoformat()
    get_table("quiz_attempts").update({"uid": attempt_uid, "expires_at": past}, ["uid"])
    refresh_snapshot()
    response = answer_correctly(player, slug, attempt_uid, question)
    assert response.status_code == 400


def test_an_expired_attempt_reads_as_expired_without_a_background_pass(app_server, seeded_db):
    from devplacepy.database import get_table, refresh_snapshot

    owner, _ = signup()
    slug = build_published(owner, title="Lazy expiry quiz", time_limit_seconds="3600")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    refresh_snapshot()
    past = (datetime.now(timezone.utc) - timedelta(seconds=5)).isoformat()
    get_table("quiz_attempts").update({"uid": attempt_uid, "expires_at": past}, ["uid"])
    refresh_snapshot()
    payload = attempt_state(player, slug, attempt_uid)
    assert payload["attempt"]["status"] == "expired"


def test_a_time_limited_attempt_reports_remaining_seconds(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Timer quiz", time_limit_seconds="600")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    payload = attempt_state(player, slug, attempt_uid)
    assert 0 < payload["attempt"]["remaining_seconds"] <= 600


def test_the_results_page_is_readable_by_the_attempt_owner(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Results quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    response = player.get(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/results", headers=JSON
    )
    assert response.status_code == 200
    assert response.json()["attempt"]["status"] == "completed"


def test_the_results_page_is_hidden_from_another_member(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Private results quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    other, _ = signup()
    response = other.get(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/results", headers=JSON
    )
    assert response.status_code == 404


def test_an_admin_can_read_the_results(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Admin results quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    admin = requests.Session()
    admin.post(
        f"{BASE_URL}/auth/login",
        data={"email": seeded_db["alice"]["email"], "password": seeded_db["alice"]["password"]},
        allow_redirects=True,
    )
    response = admin.get(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/results", headers=JSON
    )
    assert response.status_code == 200


def test_the_review_is_omitted_when_the_author_disabled_it(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="No review quiz", allow_review="0")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    payload = player.get(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/results", headers=JSON
    ).json()
    assert payload["review"] == []


def test_the_review_is_present_when_allowed(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Review quiz", allow_review="1")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    _play(player, slug, attempt_uid)
    payload = player.get(
        f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/results", headers=JSON
    ).json()
    assert len(payload["review"]) == 2


def test_the_answer_key_is_withheld_mid_attempt(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Withheld key quiz", reveal_answers="0")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    payload = attempt_state(player, slug, attempt_uid)
    for question in payload["attempt"]["questions"]:
        assert question["correct_boolean"] is None
        assert question["expected_answer"] is None
        assert question["numeric_value"] is None
        for option in question["options"]:
            assert option["is_correct"] is None
            assert option["match_value"] is None


def test_the_answer_key_reaches_the_quiz_owner(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Owner key quiz", reveal_answers="0")
    attempt_uid = start_attempt(owner, slug)
    payload = attempt_state(owner, slug, attempt_uid)
    choice = next(
        question
        for question in payload["attempt"]["questions"]
        if question["kind"] == "single_choice"
    )
    assert any(option["is_correct"] for option in choice["options"])


def test_an_answered_question_reveals_its_key_when_configured(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Reveal quiz", reveal_answers="1")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    question = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    answer_correctly(player, slug, attempt_uid, question)
    after = attempt_state(player, slug, attempt_uid)["attempt"]["questions"][0]
    assert after["numeric_value"] == 1024.0


def test_answering_a_deleted_quiz_still_uses_the_attempt_snapshot(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, title="Deleted mid attempt quiz")
    player, _ = signup()
    attempt_uid = start_attempt(player, slug)
    owner.post(f"{BASE_URL}/quizzes/delete/{slug}", headers=JSON)
    response = player.get(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}", headers=JSON)
    assert response.status_code == 404
