# retoor <retoor@molodetz.nl>

import requests

from tests.api.quizzes._helpers import (
    CHOICE,
    FREE_TEXT,
    JSON,
    NUMERIC,
    TRUE_FALSE,
    add_question,
    create_quiz,
    signup,
)
from tests.conftest import BASE_URL

FILL_BLANK = {
    "kind": "fill_blank",
    "prompt": "Complete the pragma names",
    "points": 2,
    "options": "First blank\nSecond blank",
    "match_values": "journal_mode\nsynchronous",
}

ORDERING = {
    "kind": "ordering",
    "prompt": "Order the steps",
    "points": 2,
    "options": "Open\nRead\nClose",
}

MATCHING = {
    "kind": "matching",
    "prompt": "Match the pragma to its value",
    "points": 2,
    "options": "journal_mode\nsynchronous",
    "match_values": "WAL\nNORMAL",
}


def _builder(session, slug):
    return session.get(f"{BASE_URL}/quizzes/{slug}/edit", headers=JSON).json()


def test_the_builder_requires_auth(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = requests.get(f"{BASE_URL}/quizzes/{slug}/edit", headers=JSON, allow_redirects=False)
    assert response.status_code in (401, 303, 404)


def test_the_builder_lists_the_question_kinds(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    payload = _builder(session, slug)
    assert len(payload["kinds"]) == 8
    assert {kind["key"] for kind in payload["kinds"]} >= {"single_choice", "free_text"}


def test_another_member_cannot_open_the_builder(app_server, seeded_db):
    owner, _ = signup()
    slug = create_quiz(owner)
    other, _ = signup()
    assert other.get(f"{BASE_URL}/quizzes/{slug}/edit", headers=JSON).status_code == 404


def test_adding_a_single_choice_stores_its_options(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    assert add_question(session, slug, CHOICE).status_code == 200
    question = _builder(session, slug)["questions"][0]
    assert question["kind"] == "single_choice"
    assert [option["label"] for option in question["options"]] == ["DELETE", "WAL", "MEMORY"]
    assert [option["is_correct"] for option in question["options"]] == [False, True, False]


def test_adding_a_true_false_stores_the_boolean(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, TRUE_FALSE)
    assert _builder(session, slug)["questions"][0]["correct_boolean"] is True


def test_adding_a_numeric_stores_the_value_and_tolerance(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, {**NUMERIC, "numeric_tolerance": "0.5"})
    question = _builder(session, slug)["questions"][0]
    assert question["numeric_value"] == 1024.0
    assert question["numeric_tolerance"] == 0.5


def test_adding_a_free_text_stores_the_criteria(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, FREE_TEXT)
    question = _builder(session, slug)["questions"][0]
    assert question["expected_answer"].startswith("It keeps")
    assert question["grading_criteria"].startswith("Accept")


def test_adding_a_fill_blank_stores_the_accepted_answers(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, FILL_BLANK)
    question = _builder(session, slug)["questions"][0]
    assert [option["match_value"] for option in question["options"]] == [
        "journal_mode",
        "synchronous",
    ]


def test_adding_an_ordering_stores_the_author_order(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, ORDERING)
    question = _builder(session, slug)["questions"][0]
    assert [option["label"] for option in question["options"]] == ["Open", "Read", "Close"]


def test_adding_a_matching_stores_the_pairs(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, MATCHING)
    question = _builder(session, slug)["questions"][0]
    assert [option["match_value"] for option in question["options"]] == ["WAL", "NORMAL"]


def test_a_choice_needs_at_least_two_options(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = add_question(
        session, slug, {**CHOICE, "options": "OnlyOne", "correct_indexes": "0"}
    )
    assert response.status_code == 422


def test_a_single_choice_needs_exactly_one_correct_option(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = add_question(session, slug, {**CHOICE, "correct_indexes": "0,1"})
    assert response.status_code == 422


def test_a_free_text_needs_a_reference_or_criteria(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = add_question(
        session, slug, {"kind": "free_text", "prompt": "Explain", "points": 1}
    )
    assert response.status_code == 422


def test_an_unknown_kind_is_rejected(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = add_question(session, slug, {"kind": "telepathy", "prompt": "Guess"})
    assert response.status_code == 422


def test_points_are_bounded(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    assert add_question(session, slug, {**NUMERIC, "points": "0"}).status_code == 422
    assert add_question(session, slug, {**NUMERIC, "points": "500"}).status_code == 422


def test_questions_are_appended_in_order(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    add_question(session, slug, NUMERIC)
    add_question(session, slug, TRUE_FALSE)
    positions = [question["position"] for question in _builder(session, slug)["questions"]]
    assert positions == [0, 1, 2]


def test_adding_a_question_recomputes_the_totals(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    add_question(session, slug, NUMERIC)
    quiz = _builder(session, slug)["quiz"]
    assert quiz["question_count"] == 2
    assert quiz["total_points"] == 3


def test_editing_a_question_replaces_its_options(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    question_uid = _builder(session, slug)["questions"][0]["uid"]
    session.post(
        f"{BASE_URL}/quizzes/{slug}/questions/{question_uid}",
        data={**CHOICE, "prompt": "Rewritten", "options": "A\nB", "correct_indexes": "0"},
        headers=JSON,
    )
    question = _builder(session, slug)["questions"][0]
    assert question["prompt"] == "Rewritten"
    assert [option["label"] for option in question["options"]] == ["A", "B"]


def test_editing_a_question_recomputes_the_totals(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    question_uid = _builder(session, slug)["questions"][0]["uid"]
    session.post(
        f"{BASE_URL}/quizzes/{slug}/questions/{question_uid}",
        data={**CHOICE, "points": "9"},
        headers=JSON,
    )
    assert _builder(session, slug)["quiz"]["total_points"] == 9


def test_editing_an_unknown_question_is_a_400(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = session.post(
        f"{BASE_URL}/quizzes/{slug}/questions/nope", data=CHOICE, headers=JSON
    )
    assert response.status_code == 400


def test_deleting_a_question_renumbers_the_rest(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    add_question(session, slug, NUMERIC)
    first_uid = _builder(session, slug)["questions"][0]["uid"]
    session.post(f"{BASE_URL}/quizzes/{slug}/questions/{first_uid}/delete", headers=JSON)
    questions = _builder(session, slug)["questions"]
    assert len(questions) == 1
    assert questions[0]["position"] == 0


def test_deleting_an_unknown_question_is_a_400(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = session.post(f"{BASE_URL}/quizzes/{slug}/questions/nope/delete", headers=JSON)
    assert response.status_code == 400


def test_reordering_applies_the_new_order(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    add_question(session, slug, NUMERIC)
    uids = [question["uid"] for question in _builder(session, slug)["questions"]]
    session.post(
        f"{BASE_URL}/quizzes/{slug}/questions/reorder",
        data={"order": ",".join(reversed(uids))},
        headers=JSON,
    )
    assert [q["uid"] for q in _builder(session, slug)["questions"]] == list(reversed(uids))


def test_reordering_needs_every_question(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    add_question(session, slug, NUMERIC)
    uids = [question["uid"] for question in _builder(session, slug)["questions"]]
    response = session.post(
        f"{BASE_URL}/quizzes/{slug}/questions/reorder", data={"order": uids[0]}, headers=JSON
    )
    assert response.status_code == 400


def test_reordering_needs_a_non_empty_order(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    add_question(session, slug, CHOICE)
    response = session.post(
        f"{BASE_URL}/quizzes/{slug}/questions/reorder", data={"order": ""}, headers=JSON
    )
    assert response.status_code == 422


def test_another_member_cannot_add_a_question(app_server, seeded_db):
    owner, _ = signup()
    slug = create_quiz(owner)
    other, _ = signup()
    assert add_question(other, slug, CHOICE).status_code == 404


def test_a_guest_cannot_add_a_question(app_server, seeded_db):
    session, _ = signup()
    slug = create_quiz(session)
    response = requests.post(
        f"{BASE_URL}/quizzes/{slug}/questions", data=CHOICE, headers=JSON
    )
    assert response.status_code in (401, 303)
