# retoor <retoor@molodetz.nl>

import requests

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


def _play(session, slug, correct=True):
    attempt_uid = start_attempt(session, slug)
    state = attempt_state(session, slug, attempt_uid)
    for question in state["attempt"]["questions"]:
        if correct:
            answer_correctly(session, slug, attempt_uid, question)
        else:
            session.post(
                f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/answer",
                data={"question_uid": question["uid"], "answer_text": "-1"},
                headers=JSON,
            )
    session.post(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/finish", headers=JSON)
    return attempt_uid


def _entry(username, limit=100):
    payload = requests.get(f"{BASE_URL}/quizzes/scoreboard?limit={limit}", headers=JSON).json()
    for entry in payload["scoreboard"]:
        if entry["user"]["username"] == username:
            return entry
    return None


def test_the_scoreboard_is_public(app_server, seeded_db):
    response = requests.get(f"{BASE_URL}/quizzes/scoreboard", headers=JSON)
    assert response.status_code == 200
    assert "scoreboard" in response.json()


def test_the_scoreboard_reports_its_limit(app_server, seeded_db):
    payload = requests.get(f"{BASE_URL}/quizzes/scoreboard?limit=5", headers=JSON).json()
    assert payload["limit"] == 5
    assert len(payload["scoreboard"]) <= 5


def test_the_limit_is_bounded(app_server, seeded_db):
    payload = requests.get(f"{BASE_URL}/quizzes/scoreboard?limit=9999", headers=JSON).json()
    assert payload["limit"] == 100


def test_completing_a_quiz_puts_a_member_on_the_board(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Board entry quiz")
    player, player_name = signup()
    _play(player, slug)
    entry = _entry(player_name)
    assert entry is not None
    assert entry["total_points"] == 1.0
    assert entry["quizzes_completed"] == 1


def test_the_board_projects_users_through_the_safe_schema(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Safe user quiz")
    player, player_name = signup()
    _play(player, slug)
    entry = _entry(player_name)
    assert "email" not in entry["user"]
    assert "api_key" not in entry["user"]
    assert "password_hash" not in entry["user"]


def test_the_board_cannot_be_farmed_by_replaying(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Farming quiz")
    player, player_name = signup()
    _play(player, slug)
    before = _entry(player_name)["total_points"]
    for _ in range(4):
        _play(player, slug)
    assert _entry(player_name)["total_points"] == before


def test_a_worse_replay_never_lowers_the_score(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Worse replay quiz")
    player, player_name = signup()
    _play(player, slug, correct=True)
    _play(player, slug, correct=False)
    assert _entry(player_name)["total_points"] == 1.0


def test_a_better_replay_raises_the_score(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Better replay quiz")
    player, player_name = signup()
    _play(player, slug, correct=False)
    assert _entry(player_name)["total_points"] == 0.0
    _play(player, slug, correct=True)
    assert _entry(player_name)["total_points"] == 1.0


def test_an_author_scores_on_their_own_quiz(app_server, seeded_db):
    owner, owner_name = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Own quiz scoring")
    _play(owner, slug)
    entry = _entry(owner_name)
    assert entry is not None
    assert entry["total_points"] == 1.0
    assert entry["quizzes_completed"] == 1


def test_two_quizzes_add_up(app_server, seeded_db):
    owner, _ = signup()
    first = build_published(owner, questions=(NUMERIC,), title="Sum quiz one")
    second = build_published(owner, questions=(NUMERIC,), title="Sum quiz two")
    player, player_name = signup()
    _play(player, first)
    _play(player, second)
    entry = _entry(player_name)
    assert entry["total_points"] == 2.0
    assert entry["quizzes_completed"] == 2


def test_an_unfinished_attempt_scores_nothing(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Unfinished quiz")
    player, player_name = signup()
    start_attempt(player, slug)
    assert _entry(player_name) is None


def test_the_viewer_standing_is_returned_for_a_member(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Standing quiz")
    player, player_name = signup()
    _play(player, slug)
    payload = player.get(f"{BASE_URL}/quizzes/scoreboard", headers=JSON).json()
    assert payload["viewer_standing"]["user"]["username"] == player_name


def test_the_viewer_standing_is_absent_for_a_guest(app_server, seeded_db):
    payload = requests.get(f"{BASE_URL}/quizzes/scoreboard", headers=JSON).json()
    assert payload["viewer_standing"] is None


def test_the_hub_carries_the_same_board(app_server, seeded_db):
    from devplacepy.config import QUIZ_SCOREBOARD_LIMIT

    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Hub board quiz")
    player, _ = signup()
    _play(player, slug)
    hub = requests.get(f"{BASE_URL}/quizzes", headers=JSON).json()["scoreboard"]
    board = requests.get(
        f"{BASE_URL}/quizzes/scoreboard?limit={QUIZ_SCOREBOARD_LIMIT}", headers=JSON
    ).json()["scoreboard"]
    assert len(hub) <= QUIZ_SCOREBOARD_LIMIT
    assert [entry["user"]["username"] for entry in hub] == [
        entry["user"]["username"] for entry in board
    ]


def test_the_hub_reports_the_viewer_progress(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Progress quiz")
    player, _ = signup()
    _play(player, slug)
    payload = player.get(f"{BASE_URL}/quizzes", headers=JSON).json()
    assert payload["viewer_progress"]["completed"] >= 1


def test_ranks_are_gapless(app_server, seeded_db):
    payload = requests.get(f"{BASE_URL}/quizzes/scoreboard?limit=100", headers=JSON).json()
    ranks = [entry["rank"] for entry in payload["scoreboard"]]
    assert ranks == list(range(1, len(ranks) + 1))


def test_a_perfect_run_is_counted(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Perfect quiz")
    player, player_name = signup()
    _play(player, slug)
    assert _entry(player_name)["perfect_count"] == 1


def test_completing_your_own_quiz_updates_the_progress_card(app_server, seeded_db):
    author, author_name = signup()
    slug = build_published(author, questions=(NUMERIC,), title="Own quiz progress")
    _play(author, slug)
    payload = author.get(f"{BASE_URL}/quizzes", headers=JSON).json()
    progress = payload["viewer_progress"]
    entry = _entry(author_name)
    assert entry is not None
    assert progress["completed"] == entry["quizzes_completed"] == 1
    assert progress["avg_percent"] == entry["avg_percent"] == 100.0
    assert progress["total_points"] == entry["total_points"] == 1.0
    assert progress["perfect_count"] == entry["perfect_count"] == 1
    assert payload["viewer_standing"]["user"]["username"] == author_name


def test_the_author_reaches_the_board_after_their_own_run(app_server, seeded_db):
    author, author_name = signup()
    slug = build_published(author, questions=(NUMERIC,), title="Own quiz on the board")
    assert _entry(author_name) is None
    _play(author, slug)
    assert _entry(author_name) is not None
    standing = author.get(f"{BASE_URL}/quizzes/scoreboard", headers=JSON).json()[
        "viewer_standing"
    ]
    assert standing["user"]["username"] == author_name
    assert standing["quizzes_completed"] == 1


def test_the_progress_card_matches_the_board_for_a_ranked_player(app_server, seeded_db):
    owner, _ = signup()
    slug = build_published(owner, questions=(NUMERIC,), title="Ranked progress quiz")
    player, player_name = signup()
    _play(player, slug)
    payload = player.get(f"{BASE_URL}/quizzes", headers=JSON).json()
    progress = payload["viewer_progress"]
    entry = _entry(player_name)
    assert progress["completed"] == entry["quizzes_completed"]
    assert progress["avg_percent"] == entry["avg_percent"]
    assert progress["total_points"] == entry["total_points"]
