"""
Spillerpool på organisasjonsnivå (ADR-001: gjenbrukbar på tvers av turneringer).

Kun org-medlemskap kreves (get_authorized_org) — å opprette en spiller i poolen
er organisator-arbeid, ikke noe som forutsetter rostring på et lag.

Utvidet profil (ADR-017): kontakt-/identitetsfelt brukt til å matche en
selvregistrerende spiller mot en allerede organisator-opprettet rad på
e-post (se app/routers/registration.py), og senere til å koble
app_user.id på (app/routers/auth.py sin verify_magic_link).
"""

from datetime import date

from fastapi import APIRouter, Depends
from pydantic import BaseModel, EmailStr, Field

from ..auth import get_authorized_org
from ..db import org_connection
from ..errors import app_error, translate_db_errors

router = APIRouter()


class PlayerCreate(BaseModel):
    display_name: str = Field(min_length=1, max_length=200)
    # Samme grense som ProfileUpdate.handicap_index i auth.py (WHS-maksimum).
    handicap_index: float | None = Field(default=None, ge=-10, le=54)
    gender: str | None = Field(default=None, pattern="^[mfx]$")
    mobile: str | None = Field(default=None, max_length=30)
    email: EmailStr | None = None
    birth_date: date | None = None
    nickname: str | None = Field(default=None, max_length=100)
    country: str | None = Field(default=None, max_length=100)
    club: str | None = Field(default=None, max_length=200)
    club_member_number: str | None = Field(default=None, max_length=50)
    # Utvidet registreringsskjema (2026-08-14, migrasjon 074) -- for-/
    # etternavn er en VALGFRI, separat kilde ved siden av display_name
    # (som fortsatt er det som faktisk vises overalt ellers, uendret) --
    # se migrasjonens header. paid/comment er ren registreringsadministrasjon.
    first_name: str | None = Field(default=None, max_length=100)
    last_name: str | None = Field(default=None, max_length=100)
    paid: bool = False
    comment: str | None = Field(default=None, max_length=2000)


class Player(BaseModel):
    id: str
    display_name: str
    handicap_index: float | None
    gender: str | None
    mobile: str | None
    email: str | None
    birth_date: date | None
    nickname: str | None
    country: str | None
    club: str | None
    club_member_number: str | None
    first_name: str | None
    last_name: str | None
    paid: bool
    comment: str | None


_PLAYER_COLUMNS = """
    id::text, display_name, handicap_index::float AS handicap_index, gender,
    mobile, email, birth_date, nickname, country, club, club_member_number,
    first_name, last_name, paid, comment
"""


@router.get("/orgs/{organization_id}/players", response_model=list[Player])
async def list_players(
    organization_id: str = Depends(get_authorized_org),
) -> list[Player]:
    async with org_connection(organization_id) as conn:
        rows = await conn.fetch(
            f"SELECT {_PLAYER_COLUMNS} FROM player ORDER BY display_name"
        )
    return [Player(**dict(r)) for r in rows]


@router.post("/orgs/{organization_id}/players", response_model=Player, status_code=201)
async def create_player(
    body: PlayerCreate,
    organization_id: str = Depends(get_authorized_org),
) -> Player:
    async with org_connection(organization_id) as conn, translate_db_errors():
        row = await conn.fetchrow(
            f"""
            INSERT INTO player
                (organization_id, display_name, handicap_index, gender,
                 mobile, email, birth_date, nickname, country, club, club_member_number,
                 first_name, last_name, paid, comment)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
            RETURNING {_PLAYER_COLUMNS}
            """,
            organization_id,
            body.display_name,
            body.handicap_index,
            body.gender,
            body.mobile,
            body.email.lower() if body.email else None,
            body.birth_date,
            body.nickname,
            body.country,
            body.club,
            body.club_member_number,
            body.first_name,
            body.last_name,
            body.paid,
            body.comment,
        )
    return Player(**dict(row))


class PlayerBulkResult(BaseModel):
    player: Player
    status: str  # "created" | "updated" | "overwritten"


class PlayerBulkRow(BaseModel):
    """Én rad i en masseimport. `data` er selve spillerfeltene (uendret
    `PlayerCreate`). `match_player_id`/`duplicate_action` er NYE
    (2026-08-18) -- se docstringen på `create_players_bulk` for hvorfor."""

    data: PlayerCreate
    # Satt av frontend NÅR organisatoren har blitt bedt om, og svart på, et
    # "dette ser ut som en duplikat"-varsel for denne raden (ingen e-post på
    # raden, men navnet traff en eksisterende spiller i poolen) -- IKKE satt
    # for rader uten et slikt varsel, som fortsatt går via den opprinnelige
    # e-post-matchingen under.
    match_player_id: str | None = None
    duplicate_action: str | None = Field(default=None, pattern="^(merge|overwrite|create_new)$")


@router.post("/orgs/{organization_id}/players/bulk", response_model=list[PlayerBulkResult], status_code=201)
async def create_players_bulk(
    body: list[PlayerBulkRow],
    organization_id: str = Depends(get_authorized_org),
) -> list[PlayerBulkResult]:
    """Massimport (CSV-basert, se FEATURE_BACKLOG.md). Samme "match på
    e-post, fyll KUN tomme felt, ellers opprett ny"-mønster som
    `registration.py` sin selvregistrering (ADR-017 Beslutning B) -- her
    utvidet til ALLE `PlayerCreate`-felt (registration.py sin variant
    mangler first_name/last_name/paid/comment, som ikke fantes da den ble
    skrevet). `display_name` og `paid` røres ALDRI på en eksisterende
    match via COALESCE-stien -- førstnevnte er organisators kanoniske navn,
    sistnevnte er en ikke-nullbar boolean (COALESCE mot NULL er da alltid
    en no-op, som er riktig oppførsel: en import skal ikke stille kunne
    endre en allerede satt betalt-status). Hele lista i én transaksjon
    (org_connection sin egen) -- ingen delvis-suksess-håndtering, brukeren
    har allerede validert/rettet radene i tabellen før "Lagre" trykkes.

    `match_player_id`/`duplicate_action` (2026-08-18) -- ekte bug funnet i
    produksjon: rader UTEN e-post har ingen matchingsnøkkel i det hele
    tatt, så reimport av samme CSV la dem inn på nytt hver gang i stedet
    for å oppdatere (ni spillere dublert i org 8db22cb9 ved reimport).
    Fikset ved å la FRONTEND oppdage mulige duplikater (ingen e-post +
    navnetreff mot en eksisterende spiller i poolen) og SPØRRE organisator
    eksplisitt om hva som skal skje -- IKKE en stille automatisk
    sammenslåing på navn alene (for risikabelt: to ulike ekte personer kan
    dele navn). Svaret sendes tilbake her som `match_player_id` (hvilken
    eksisterende spiller raden gjelder) + `duplicate_action`:
    - "merge": samme COALESCE-oppførsel som e-post-matching allerede har
      (fyll kun tomme felt på den eksisterende raden).
    - "overwrite": erstatt ALLE feltene på den eksisterende raden med
      verdiene fra denne importraden, også der den eksisterende raden
      allerede hadde en verdi -- eneste sted i denne funksjonen som gjør
      det, og KUN når organisator eksplisitt har bedt om det for akkurat
      denne raden.
    - "create_new" (eller `match_player_id` ikke satt i det hele tatt):
      uendret -- opprett en ny rad, som før.
    Rader MED e-post er upåvirket -- e-post-matching er entydig nok til at
    den fortsatt skjer automatisk, ingen spørring nødvendig der."""
    results: list[PlayerBulkResult] = []
    async with org_connection(organization_id) as conn, translate_db_errors():
        for item in body:
            p = item.data
            existing = None
            if p.email:
                existing = await conn.fetchrow(
                    "SELECT id FROM player WHERE lower(email) = lower($1)", p.email
                )
            elif item.match_player_id and item.duplicate_action in ("merge", "overwrite"):
                existing = {"id": item.match_player_id}

            if existing is not None and item.duplicate_action == "overwrite":
                row = await conn.fetchrow(
                    f"""
                    UPDATE player SET
                        handicap_index = $2, gender = $3, mobile = $4, birth_date = $5,
                        nickname = $6, country = $7, club = $8, club_member_number = $9,
                        first_name = $10, last_name = $11, comment = $12
                    WHERE id = $1
                    RETURNING {_PLAYER_COLUMNS}
                    """,
                    existing["id"],
                    p.handicap_index,
                    p.gender,
                    p.mobile,
                    p.birth_date,
                    p.nickname,
                    p.country,
                    p.club,
                    p.club_member_number,
                    p.first_name,
                    p.last_name,
                    p.comment,
                )
                results.append(PlayerBulkResult(player=Player(**dict(row)), status="overwritten"))
            elif existing is not None:
                row = await conn.fetchrow(
                    f"""
                    UPDATE player SET
                        handicap_index = COALESCE(handicap_index, $2),
                        gender = COALESCE(gender, $3),
                        mobile = COALESCE(mobile, $4),
                        birth_date = COALESCE(birth_date, $5),
                        nickname = COALESCE(nickname, $6),
                        country = COALESCE(country, $7),
                        club = COALESCE(club, $8),
                        club_member_number = COALESCE(club_member_number, $9),
                        first_name = COALESCE(first_name, $10),
                        last_name = COALESCE(last_name, $11),
                        comment = COALESCE(comment, $12)
                    WHERE id = $1
                    RETURNING {_PLAYER_COLUMNS}
                    """,
                    existing["id"],
                    p.handicap_index,
                    p.gender,
                    p.mobile,
                    p.birth_date,
                    p.nickname,
                    p.country,
                    p.club,
                    p.club_member_number,
                    p.first_name,
                    p.last_name,
                    p.comment,
                )
                results.append(PlayerBulkResult(player=Player(**dict(row)), status="updated"))
            else:
                row = await conn.fetchrow(
                    f"""
                    INSERT INTO player
                        (organization_id, display_name, handicap_index, gender,
                         mobile, email, birth_date, nickname, country, club, club_member_number,
                         first_name, last_name, paid, comment)
                    VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
                    RETURNING {_PLAYER_COLUMNS}
                    """,
                    organization_id,
                    p.display_name,
                    p.handicap_index,
                    p.gender,
                    p.mobile,
                    p.email.lower() if p.email else None,
                    p.birth_date,
                    p.nickname,
                    p.country,
                    p.club,
                    p.club_member_number,
                    p.first_name,
                    p.last_name,
                    p.paid,
                    p.comment,
                )
                results.append(PlayerBulkResult(player=Player(**dict(row)), status="created"))
    return results


class PlayerUpdate(BaseModel):
    """Alle felt valgfrie -- kun de som faktisk sendes med blir endret
    (PATCH-semantikk via Pydantic sin exclude_unset, se update_player).

    Endrer KUN player-raden (spillerpoolen) -- rører ALDRI en allerede
    frosset team_roster.handicap_index_snapshot (ADR-007): en HCP-
    korrigering her slår derfor ikke inn på et lag spilleren allerede er
    rostret på i en turnering, kun ved FREMTIDIG rostring. Fjern og legg
    til spilleren på nytt på laget for å oppdatere en allerede frosset
    snapshot-verdi.
    """

    display_name: str | None = Field(default=None, min_length=1, max_length=200)
    handicap_index: float | None = Field(default=None, ge=-10, le=54)
    gender: str | None = Field(default=None, pattern="^[mfx]$")
    mobile: str | None = Field(default=None, max_length=30)
    email: EmailStr | None = None
    birth_date: date | None = None
    nickname: str | None = Field(default=None, max_length=100)
    country: str | None = Field(default=None, max_length=100)
    club: str | None = Field(default=None, max_length=200)
    club_member_number: str | None = Field(default=None, max_length=50)
    first_name: str | None = Field(default=None, max_length=100)
    last_name: str | None = Field(default=None, max_length=100)
    paid: bool | None = None
    comment: str | None = Field(default=None, max_length=2000)


@router.patch("/orgs/{organization_id}/players/{player_id}", response_model=Player)
async def update_player(
    player_id: str,
    body: PlayerUpdate,
    organization_id: str = Depends(get_authorized_org),
) -> Player:
    updates = body.model_dump(exclude_unset=True)
    if not updates:
        raise app_error(400, "VALIDATION_FAILED", "Ingen felt å oppdatere.")
    if updates.get("email") is not None:
        updates["email"] = updates["email"].lower()

    set_clauses = [f"{key} = ${i}" for i, key in enumerate(updates, start=1)]
    values = list(updates.values())
    values.append(player_id)

    async with org_connection(organization_id) as conn, translate_db_errors():
        row = await conn.fetchrow(
            f"""
            UPDATE player SET {", ".join(set_clauses)}
            WHERE id = ${len(values)}
            RETURNING {_PLAYER_COLUMNS}
            """,
            *values,
        )
        if row is None:
            raise app_error(404, "NOT_FOUND", "Spilleren finnes ikke.")
    return Player(**dict(row))
