"""
Spillerens per-hull-historikk (ADR-072, migrasjon 075/ADR-071-tillegg):
"hvordan spiller jeg egentlig dette hullet?" -- aggregerer ALLE tidligere
ganger en spiller har spilt et gitt hull, på tvers av BÅDE frittstående
runder OG org-turneringer (på tvers av ALLE organisasjoner spilleren har
spilt turnering i), begrenset til ordinære (teeoff-/GolfAPI-importerte)
baner. Custom/håndlagde baner har ingen pålitelig felles identitet på
tvers av de to systemene og telles BEVISST ikke med (bekreftet med
bruker via AskUserQuestion).

Delt mellom rounds.py (frittstående kontekst) og individual_tournaments.py
(turnering-kontekst) -- begge kaller inn hit og får samme KOMBINERTE
historikk uansett hvilken side spørringen kom fra ("alle gangene" betyr
alle gangene, ikke bare de av typen man akkurat nå ser på).

Bane-bro mellom de to identitetssystemene (se ADR-072 for utledningen):
  - Teeoff: round.teeoff_facility_slug + round.teeoff_course_id  <->
    course.external_course_ref = f"{facility_slug}:{teeoff_course_id}"
    (course.source = 'official') -- eksakt samme streng-format som
    import_official_course() i courses.py bygger den med.
  - GolfAPI: personal_course.external_golfapi_course_id  <->
    course.external_course_ref (course.source = 'international') --
    begge lagrer samme rå golfapi_course_id, uten prefiks.
Custom personal_course (external_golfapi_course_id IS NULL) og custom
course (source = 'custom') har INGEN bro her -- ekskluderes (resolve-
funksjonene returnerer None, kallerne skjuler da historikk-panelet).

`CourseKey`/de tre `resolve_*_course_key()`-funksjonene gjenbrukes også av
`app/target_points.py` (migrasjon 079, rangefinder for offisielle baner)
-- samme bane-bro, annet formål (koordinater, ikke historikk). Tredje
søster `resolve_match_course_key()` (lag-matchplay: match->session->
course) har INGEN egen hole-history-bruker ennå (kun ADR-072s to
flyter), men følger identisk mønster for symmetriens skyld.

GIR/fairway-formlene speiler round-stats.tsx/rounds.py sin ETABLERTE
`score - putts <= par - 2`-definisjon (IKKE skjema-kommentaren i
020_personal_rounds.sql, som beskriver en avvikende, ubrukt formel --
se ADR-071/ADR-033 Beslutning B for hvorfor avviket er bevisst).
"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import date
from typing import Literal

from asyncpg import Connection
from pydantic import BaseModel

from .db import org_connection, plain_connection

CourseKey = tuple[Literal["teeoff"], str, str] | tuple[Literal["golfapi"], str]


async def resolve_personal_round_course_key(conn: Connection, round_id: str) -> CourseKey | None:
    row = await conn.fetchrow(
        """
        SELECT course_source, teeoff_facility_slug, teeoff_course_id, personal_course_id::text AS personal_course_id
        FROM round WHERE id = $1
        """,
        round_id,
    )
    if row is None:
        return None
    if row["course_source"] == "teeoff":
        return ("teeoff", row["teeoff_facility_slug"], row["teeoff_course_id"])
    golfapi_id = await conn.fetchval(
        "SELECT external_golfapi_course_id FROM personal_course WHERE id = $1",
        row["personal_course_id"],
    )
    if golfapi_id is None:
        return None
    return ("golfapi", golfapi_id)


async def resolve_tournament_round_course_key(conn: Connection, tournament_round_id: str) -> CourseKey | None:
    row = await conn.fetchrow(
        """
        SELECT c.source, c.external_course_ref
        FROM tournament_round tr
        JOIN course c ON c.id = tr.course_id
        WHERE tr.id = $1
        """,
        tournament_round_id,
    )
    if row is None or row["source"] == "custom" or row["external_course_ref"] is None:
        return None
    if row["source"] == "official":
        facility_slug, _, teeoff_course_id = row["external_course_ref"].partition(":")
        return ("teeoff", facility_slug, teeoff_course_id)
    return ("golfapi", row["external_course_ref"])


async def resolve_match_course_key(conn: Connection, match_id: str) -> CourseKey | None:
    row = await conn.fetchrow(
        """
        SELECT c.source, c.external_course_ref
        FROM match m
        JOIN session s ON s.id = m.session_id
        JOIN course c ON c.id = s.course_id
        WHERE m.id = $1
        """,
        match_id,
    )
    if row is None or row["source"] == "custom" or row["external_course_ref"] is None:
        return None
    if row["source"] == "official":
        facility_slug, _, teeoff_course_id = row["external_course_ref"].partition(":")
        return ("teeoff", facility_slug, teeoff_course_id)
    return ("golfapi", row["external_course_ref"])


@dataclass
class HoleInstance:
    source: Literal["personal", "tournament"]
    played_at: date | None
    label: str
    score: int
    par: int
    putts: int | None
    tee_shot_result: str | None
    approach_result: str | None
    chip_count: int | None
    bunker_shot_count: int | None
    penalty_strokes: int | None
    first_putt_distance_bucket: str | None


async def fetch_personal_hole_history(user_id: str, key: CourseKey, hole_number: int) -> list[HoleInstance]:
    async with plain_connection() as conn:
        if key[0] == "teeoff":
            _, facility_slug, teeoff_course_id = key
            rows = await conn.fetch(
                """
                SELECT r.played_at, r.name, rh.score, rh.par, rh.putts, rh.tee_shot_result,
                       rh.approach_result, rh.chip_count, rh.bunker_shot_count,
                       rh.penalty_strokes, rh.first_putt_distance_bucket
                FROM round_hole rh
                JOIN round_participant rp ON rp.id = rh.round_participant_id
                JOIN round r ON r.id = rp.round_id
                WHERE rp.user_id = $1 AND rh.hole_number = $2 AND rh.played = true
                  AND rh.score IS NOT NULL
                  AND r.course_source = 'teeoff'
                  AND r.teeoff_facility_slug = $3 AND r.teeoff_course_id = $4
                ORDER BY r.played_at DESC
                """,
                user_id, hole_number, facility_slug, teeoff_course_id,
            )
        else:
            _, golfapi_id = key
            rows = await conn.fetch(
                """
                SELECT r.played_at, r.name, rh.score, rh.par, rh.putts, rh.tee_shot_result,
                       rh.approach_result, rh.chip_count, rh.bunker_shot_count,
                       rh.penalty_strokes, rh.first_putt_distance_bucket
                FROM round_hole rh
                JOIN round_participant rp ON rp.id = rh.round_participant_id
                JOIN round r ON r.id = rp.round_id
                JOIN personal_course pc ON pc.id = r.personal_course_id
                WHERE rp.user_id = $1 AND rh.hole_number = $2 AND rh.played = true
                  AND rh.score IS NOT NULL
                  AND r.course_source = 'custom' AND pc.external_golfapi_course_id = $3
                ORDER BY r.played_at DESC
                """,
                user_id, hole_number, golfapi_id,
            )
    return [
        HoleInstance(
            source="personal",
            played_at=r["played_at"],
            label=r["name"] or "Runde",
            score=r["score"],
            par=r["par"],
            putts=r["putts"],
            tee_shot_result=r["tee_shot_result"],
            approach_result=r["approach_result"],
            chip_count=r["chip_count"],
            bunker_shot_count=r["bunker_shot_count"],
            penalty_strokes=r["penalty_strokes"],
            first_putt_distance_bucket=r["first_putt_distance_bucket"],
        )
        for r in rows
    ]


async def fetch_tournament_hole_history(user_id: str, key: CourseKey, hole_number: int) -> list[HoleInstance]:
    """N+1-per-org (samme mønster som /auth/me): tournament_round_hole er
    org-scopet/RLS-beskyttet, og spilleren kan ha spilt turnering i flere
    organisasjoner -- ett enkelt org_connection()-kall kan derfor aldri
    dekke alle. `player_organizations_for_user()` (migrasjon 015, SAMME
    SECURITY DEFINER-bro /auth/me allerede bruker) gir org-listen trygt
    uten en RLS-omgåelse i selve spørringen."""
    if key[0] == "teeoff":
        _, facility_slug, teeoff_course_id = key
        source_filter = "official"
        external_ref = f"{facility_slug}:{teeoff_course_id}"
    else:
        _, golfapi_id = key
        source_filter = "international"
        external_ref = golfapi_id

    async with plain_connection() as conn:
        org_rows = await conn.fetch(
            "SELECT organization_id::text AS organization_id FROM player_organizations_for_user($1)",
            user_id,
        )

    instances: list[HoleInstance] = []
    for org_row in org_rows:
        async with org_connection(org_row["organization_id"]) as conn:
            rows = await conn.fetch(
                """
                SELECT t.name AS tournament_name, tr.scheduled_at,
                       trh.gross_strokes AS score, h.par, trh.putts, trh.tee_shot_result,
                       trh.approach_result, trh.chip_count, trh.bunker_shot_count,
                       trh.penalty_strokes, trh.first_putt_distance_bucket
                FROM tournament_round_hole trh
                JOIN tournament_round_participant trp ON trp.id = trh.tournament_round_participant_id
                JOIN tournament_round tr ON tr.id = trp.tournament_round_id
                JOIN tournament t ON t.id = tr.tournament_id
                JOIN tournament_participant tp ON tp.id = trp.tournament_participant_id
                JOIN player p ON p.id = tp.player_id
                JOIN course c ON c.id = tr.course_id
                JOIN hole h ON h.course_id = tr.course_id AND h.hole_number = trh.hole_number
                WHERE p.user_id = $1 AND trh.hole_number = $2
                  AND c.source = $3 AND c.external_course_ref = $4
                ORDER BY tr.scheduled_at DESC NULLS LAST
                """,
                user_id, hole_number, source_filter, external_ref,
            )
        for r in rows:
            instances.append(
                HoleInstance(
                    source="tournament",
                    played_at=r["scheduled_at"].date() if r["scheduled_at"] is not None else None,
                    label=r["tournament_name"] or "Turnering",
                    score=r["score"],
                    par=r["par"],
                    putts=r["putts"],
                    tee_shot_result=r["tee_shot_result"],
                    approach_result=r["approach_result"],
                    chip_count=r["chip_count"],
                    bunker_shot_count=r["bunker_shot_count"],
                    penalty_strokes=r["penalty_strokes"],
                    first_putt_distance_bucket=r["first_putt_distance_bucket"],
                )
            )
    return instances


class HoleHistoryInstanceOut(BaseModel):
    source: Literal["personal", "tournament"]
    played_at: date | None
    label: str
    score: int
    par: int
    putts: int | None
    tee_shot_result: str | None
    approach_result: str | None
    chip_count: int | None
    bunker_shot_count: int | None
    penalty_strokes: int | None
    first_putt_distance_bucket: str | None


class HoleHistoryOut(BaseModel):
    times_played: int
    times_personal: int
    times_tournament: int
    average_score: float
    average_score_vs_par: float
    # None = ingen av instansene har putter registrert (rene strokes_only-
    # historikk) -- IKKE det samme som 0%.
    gir_percent: float | None
    # Kun meningsfullt for par 4/5 -- None hvis ingen slike instanser har
    # utslagsretning registrert.
    fairway_hit_percent: float | None
    average_putts: float | None
    best_score: int
    worst_score: int
    instances: list[HoleHistoryInstanceOut]


def summarize_hole_history(instances: list[HoleInstance]) -> HoleHistoryOut | None:
    if not instances:
        return None

    ordered = sorted(instances, key=lambda i: i.played_at or date.min, reverse=True)
    times_played = len(ordered)
    times_personal = sum(1 for i in ordered if i.source == "personal")
    scores = [i.score for i in ordered]

    gir_rows = [i for i in ordered if i.putts is not None]
    fairway_rows = [i for i in ordered if i.par != 3 and i.tee_shot_result is not None]
    putts_values = [i.putts for i in ordered if i.putts is not None]

    return HoleHistoryOut(
        times_played=times_played,
        times_personal=times_personal,
        times_tournament=times_played - times_personal,
        average_score=sum(scores) / times_played,
        average_score_vs_par=sum(i.score - i.par for i in ordered) / times_played,
        gir_percent=(
            100 * sum(1 for i in gir_rows if i.score - i.putts <= i.par - 2) / len(gir_rows)
            if gir_rows
            else None
        ),
        fairway_hit_percent=(
            100 * sum(1 for i in fairway_rows if i.tee_shot_result == "fairway") / len(fairway_rows)
            if fairway_rows
            else None
        ),
        average_putts=sum(putts_values) / len(putts_values) if putts_values else None,
        best_score=min(scores),
        worst_score=max(scores),
        instances=[
            HoleHistoryInstanceOut(
                source=i.source,
                played_at=i.played_at,
                label=i.label,
                score=i.score,
                par=i.par,
                putts=i.putts,
                tee_shot_result=i.tee_shot_result,
                approach_result=i.approach_result,
                chip_count=i.chip_count,
                bunker_shot_count=i.bunker_shot_count,
                penalty_strokes=i.penalty_strokes,
                first_putt_distance_bucket=i.first_putt_distance_bucket,
            )
            for i in ordered
        ],
    )


async def hole_history_for_user(user_id: str, key: CourseKey | None, hole_number: int) -> HoleHistoryOut | None:
    """Toppnivå-inngang brukt av begge routerne: løser INGENTING selv (nøkkel
    må allerede være resolvet av kalleren, som kjenner sin egen kontekst) --
    henter fra begge kilder og slår sammen. Returnerer None både når nøkkelen
    er None (custom bane) OG når spilleren ikke har noen historikk ennå --
    kalleren skiller ikke mellom disse to, begge betyr "ikke vis panelet".
    """
    if key is None:
        return None
    personal = await fetch_personal_hole_history(user_id, key, hole_number)
    tournament = await fetch_tournament_hole_history(user_id, key, hole_number)
    return summarize_hole_history(personal + tournament)
