"""
Cut (migrasjon 083, 2026-08-18): topp N og delt plass, etter et
organisator-valgt rundenummer, MANUELT anvendt (bruker bekreftet
eksplisitt -- ikke automatisk ved fullspilt runde). Kaller de ekte
router-funksjonene direkte (samme mønster som test_order_of_merit.py).
"""

import pytest
from fastapi import HTTPException

from app.auth import CurrentUser
from app.routers.individual_tournaments import (
    HoleUpdate as TournamentHoleUpdate,
    apply_cut,
    individual_leaderboard,
    update_hole as tournament_update_hole,
)
from app.routers.tournaments import TournamentUpdate, update_tournament

from tests.conftest import (
    add_membership,
    create_org,
    create_org_hole,
    create_course,
    create_player,
    create_tee,
    create_tournament,
    create_tournament_participant,
    create_tournament_round,
    create_tournament_round_participant,
    create_user,
)

import app.db as app_db


async def _score_holes(tournament_id, round_id, rp_id, org_id, user_id, scores: list[int]) -> None:
    user = CurrentUser(user_id=user_id)
    for n, gross in enumerate(scores, start=1):
        await tournament_update_hole(
            tournament_id, round_id, rp_id, n,
            TournamentHoleUpdate(gross_strokes=gross, expected_version=None),
            organization_id=org_id, user=user,
        )


async def _setup(org_id, owner_id, method="stroke_gross"):
    tournament_id = await create_tournament(org_id, name="Cut-turnering")
    course_id = await create_course(org_id, name="Cut Links")
    for n in range(1, 19):
        await create_org_hole(org_id, course_id, hole_number=n, par=4, stroke_index=n)
    tee_id = await create_tee(org_id, course_id)
    async with app_db.org_connection(org_id) as conn:
        await conn.execute(
            "UPDATE tournament SET format_type = 'individual', scoring_method = $2 WHERE id = $1",
            tournament_id, method,
        )
    round1_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1)
    round2_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=2)

    players = {}
    for name in ["P1", "P2", "P3", "P4", "P5"]:
        player_id = await create_player(org_id, display_name=name)
        tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
        players[name] = tp_id

    # Runde 1: P1 -4, P2/P3 uavgjort -2, P4 +3, P5 +8 (par 72 over 18 hull).
    scores_r1 = {
        "P1": [3, 3, 3, 3] + [4] * 14,   # 68 -> -4
        "P2": [3, 3] + [4] * 16,         # 70 -> -2
        "P3": [3, 3] + [4] * 16,         # 70 -> -2 (uavgjort med P2)
        "P4": [5, 5, 5] + [4] * 15,      # 75 -> +3
        "P5": [5] * 8 + [4] * 10,        # 80 -> +8
    }
    rp1 = {}
    for name, tp_id in players.items():
        rp1[name] = await create_tournament_round_participant(org_id, round1_id, tp_id, tee_id)
        await _score_holes(tournament_id, round1_id, rp1[name], org_id, owner_id, scores_r1[name])

    # P6 opprettes som deltaker, men spiller ALDRI runde 1 -- skal kuttes
    # uansett cut_size (ingen resultat gjennom cut-punktet).
    p6_player_id = await create_player(org_id, display_name="P6")
    players["P6"] = await create_tournament_participant(org_id, tournament_id, p6_player_id)

    rp2 = {name: await create_tournament_round_participant(org_id, round2_id, tp_id, tee_id)
           for name, tp_id in players.items() if name != "P6"}

    return tournament_id, round1_id, round2_id, players, rp2


async def _set_cut_config(org_id, tournament_id, cut_after_round, cut_size):
    await update_tournament(
        tournament_id,
        TournamentUpdate(cut_after_round=cut_after_round, cut_size=cut_size),
        organization_id=org_id,
    )


async def test_apply_cut_top_n_and_ties_lets_extra_player_through(pool):
    """cut_size=2, men P2/P3 er uavgjort på grenseplassen -- begge skal
    slippe gjennom ("delt plass"), så tre (ikke to) overlever i praksis."""
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
    await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)

    result = await apply_cut(tournament_id, organization_id=org_id)
    assert result.cut_after_round == 1
    assert result.cut_size == 2
    assert result.survivors == 3  # P1, P2, P3 (uavgjort holdt begge inne)
    assert result.cut_count == 3  # P4, P5, P6

    async with app_db.org_connection(org_id) as conn:
        cut_flags = {
            name: await conn.fetchval("SELECT cut FROM tournament_participant WHERE id = $1", tp_id)
            for name, tp_id in players.items()
        }
    assert cut_flags["P1"] is False
    assert cut_flags["P2"] is False
    assert cut_flags["P3"] is False
    assert cut_flags["P4"] is True
    assert cut_flags["P5"] is True
    assert cut_flags["P6"] is True  # spilte aldri runde 1 -- kuttes uansett cut_size


async def test_apply_cut_is_idempotent(pool):
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
    await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)

    first = await apply_cut(tournament_id, organization_id=org_id)
    second = await apply_cut(tournament_id, organization_id=org_id)
    assert first.cut_participant_ids == second.cut_participant_ids or (
        set(first.cut_participant_ids) == set(second.cut_participant_ids)
    )
    assert second.survivors == first.survivors
    assert second.cut_count == first.cut_count


async def test_apply_cut_requires_config_set_first(pool):
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    tournament_id, *_ = await _setup(org_id, owner_id)

    with pytest.raises(HTTPException) as exc_info:
        await apply_cut(tournament_id, organization_id=org_id)
    assert exc_info.value.status_code == 400


async def test_cut_player_blocked_from_scoring_in_later_round(pool):
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
    await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
    await apply_cut(tournament_id, organization_id=org_id)

    user = CurrentUser(user_id=owner_id)
    # P4 er kuttet -- runde 2 (etter cut_after_round=1) skal avvises.
    with pytest.raises(HTTPException) as exc_info:
        await tournament_update_hole(
            tournament_id, round2_id, rp2["P4"], 1,
            TournamentHoleUpdate(gross_strokes=4, expected_version=None),
            organization_id=org_id, user=user,
        )
    assert exc_info.value.status_code == 403

    # P1 overlevde cutten -- runde 2 skal fortsatt fungere som normalt.
    result = await tournament_update_hole(
        tournament_id, round2_id, rp2["P1"], 1,
        TournamentHoleUpdate(gross_strokes=4, expected_version=None),
        organization_id=org_id, user=user,
    )
    assert result.gross_strokes == 4


async def test_cut_player_can_still_edit_score_in_round_at_or_before_cut_point(pool):
    """Cutten låser IKKE historikken -- en rettelse i runde 1 (t.o.m.
    cut_after_round) skal fortsatt være mulig for en kuttet spiller."""
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
    await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
    await apply_cut(tournament_id, organization_id=org_id)

    async with app_db.org_connection(org_id) as conn:
        rp1_p4 = await conn.fetchval(
            "SELECT id FROM tournament_round_participant WHERE tournament_round_id = $1 AND tournament_participant_id = $2",
            round1_id, players["P4"],
        )
    user = CurrentUser(user_id=owner_id)
    result = await tournament_update_hole(
        tournament_id, round1_id, str(rp1_p4), 1,
        TournamentHoleUpdate(gross_strokes=5, expected_version=1),
        organization_id=org_id, user=user,
    )
    assert result.gross_strokes == 5


async def test_leaderboard_marks_cut_players_with_status_and_sorts_them_last(pool):
    org_id = await create_org()
    owner_id = await create_user()
    await add_membership(org_id, owner_id, role="owner")
    tournament_id, round1_id, round2_id, players, rp2 = await _setup(org_id, owner_id)
    await _set_cut_config(org_id, tournament_id, cut_after_round=1, cut_size=2)
    await apply_cut(tournament_id, organization_id=org_id)

    entries = await individual_leaderboard(tournament_id, organization_id=org_id)
    by_id = {e.tournament_participant_id: e for e in entries}

    assert by_id[players["P4"]].total_label == "CUT"
    assert by_id[players["P4"]].cut is True
    assert by_id[players["P4"]].is_leader is False
    assert by_id[players["P1"]].total_label != "CUT"
    assert by_id[players["P1"]].cut is False

    # Alle ikke-kuttede rader kommer FØR alle kuttede rader, uansett
    # frossent til-par-tall.
    cut_flags_in_order = [by_id[e.tournament_participant_id].cut for e in entries]
    first_cut_index = cut_flags_in_order.index(True)
    assert all(not c for c in cut_flags_in_order[:first_cut_index])
    assert all(c for c in cut_flags_in_order[first_cut_index:])
