"""
Organisasjon-bootstrap (opprett en ny organisasjon og bli owner).

Eneste stedet i API-et som setter inn en `organization`-rad. Bruker
`get_current_user` (IKKE `get_authorized_org`, som forutsetter en ALLEREDE
eksisterende org-medlemskap -- sirkulært for akkurat denne handlingen, samme
resonnement som lag-/roster-opprettelse i oppsett-runden).

Selvrefererende RLS-bootstrap: `org_self`-policyen (etter migrasjon 005) er
`USING (id = app_current_org())` uten eksplisitt WITH CHECK, som Postgres
speiler dynamisk til INSERT-sjekken. Genererer derfor org-ens uuid FØR
innsetting og setter `app.current_org` til NØYAKTIG den verdien via den
allerede eksisterende `org_connection()` -- da er WITH CHECK trivielt sann
(id == current_org), og `teecup_app` (NOSUPERUSER/NOBYPASSRLS) kan sette inn
sin egen FØRSTE organisasjonsrad uten noen egen privilegert tilkobling.
Verifisert med en negativ kontroll (mismatchende id blir avvist) i
scratch-testingen -- se plan.
"""

import traceback
import uuid

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

from ..auth import CurrentUser, get_authorized_org, get_current_user, get_superadmin_user
from ..config import settings
from ..db import org_connection
from ..email import send_organization_invitation_email
from ..errors import app_error, translate_db_errors

router = APIRouter()


class OrganizationCreate(BaseModel):
    name: str = Field(min_length=1, max_length=200)


class OrganizationOut(BaseModel):
    id: str
    name: str
    role: str


@router.post("/orgs", response_model=OrganizationOut, status_code=201)
async def create_organization(
    body: OrganizationCreate,
    user: CurrentUser = Depends(get_current_user),
) -> OrganizationOut:
    new_id = str(uuid.uuid4())

    async with org_connection(new_id) as conn, translate_db_errors():
        row = await conn.fetchrow(
            "INSERT INTO organization (id, name) VALUES ($1, $2) RETURNING id::text, name",
            new_id,
            body.name,
        )
        await conn.execute(
            "INSERT INTO organization_membership (organization_id, user_id, role) VALUES ($1, $2, 'owner')",
            new_id,
            user.user_id,
        )

    return OrganizationOut(id=row["id"], name=row["name"], role="owner")


class OrganizationUpdate(BaseModel):
    """Alle felt valgfrie -- kun de som faktisk sendes med blir endret
    (exclude_unset, se update_organization). Foreløpig kun de to feltene
    landingssiden trenger (ADR-018) -- ikke en generell org-innstillings-
    endepunkt."""

    public_profile: bool | None = None
    slug: str | None = Field(default=None, pattern="^[a-z0-9]+(-[a-z0-9]+)*$")


class OrganizationSettings(BaseModel):
    id: str
    name: str
    public_profile: bool
    slug: str | None


@router.patch("/orgs/{organization_id}", response_model=OrganizationSettings)
async def update_organization(
    body: OrganizationUpdate,
    organization_id: str = Depends(get_authorized_org),
) -> OrganizationSettings:
    updates = body.model_dump(exclude_unset=True)
    if not updates:
        raise app_error(400, "VALIDATION_FAILED", "Ingen felt å oppdatere.")

    # Trygt mot injeksjon: nøklene kommer KUN fra OrganizationUpdate sine
    # egne feltnavn (fast, kjent hviteliste).
    set_clauses = [f"{key} = ${i}" for i, key in enumerate(updates, start=1)]
    values = list(updates.values())
    values.append(organization_id)

    async with org_connection(organization_id) as conn, translate_db_errors():
        row = await conn.fetchrow(
            f"""
            UPDATE organization SET {', '.join(set_clauses)}
            WHERE id = ${len(values)}
            RETURNING id::text, name, public_profile, slug
            """,
            *values,
        )
        if row is None:
            raise app_error(404, "NOT_FOUND", "Organisasjonen finnes ikke.")
    return OrganizationSettings(**dict(row))


# ---------------------------------------------------------------------------
# Medlemskap (ADR-022) -- rollestyring, frasi seg eierskap, forlate org.
# ---------------------------------------------------------------------------


async def _caller_role(conn, organization_id: str, user_id: str) -> str | None:
    return await conn.fetchval(
        "SELECT role FROM organization_membership WHERE organization_id = $1 AND user_id = $2",
        organization_id,
        user_id,
    )


async def _owner_count_locked(conn, organization_id: str) -> int:
    """Låser alle eier-rader for organisasjonen for resten av transaksjonen --
    TOCTOU-trygt mot at to samtidige nedgraderinger begge tror det finnes
    minst to eiere. Samme mønster som ADR-011s to-lags-grense."""
    rows = await conn.fetch(
        "SELECT id FROM organization_membership WHERE organization_id = $1 AND role = 'owner' FOR UPDATE",
        organization_id,
    )
    return len(rows)


_ROLE_RANK = {"member": 0, "admin": 1, "owner": 2}


class MembershipOut(BaseModel):
    id: str
    user_id: str
    email: str
    display_name: str
    role: str


@router.get("/orgs/{organization_id}/memberships", response_model=list[MembershipOut])
async def list_memberships(organization_id: str = Depends(get_authorized_org)) -> list[MembershipOut]:
    async with org_connection(organization_id) as conn:
        rows = await conn.fetch(
            """
            SELECT om.id::text AS id, om.user_id::text AS user_id, om.role,
                   u.email::text AS email, u.display_name
            FROM organization_membership om
            JOIN app_user u ON u.id = om.user_id
            WHERE om.organization_id = $1
            ORDER BY u.display_name
            """,
            organization_id,
        )
    return [MembershipOut(**dict(r)) for r in rows]


class MembershipUpdate(BaseModel):
    role: str = Field(pattern="^(owner|admin|member)$")


@router.patch("/orgs/{organization_id}/memberships/{membership_id}", response_model=MembershipOut)
async def update_membership(
    membership_id: str,
    body: MembershipUpdate,
    organization_id: str = Depends(get_authorized_org),
    user: CurrentUser = Depends(get_current_user),
) -> MembershipOut:
    """Eier kan endre ENHVER rolle. En bruker kan alltid SENKE sin egen rolle
    selv (frasi seg eierskap er selvbetjent, ADR-022 Beslutning C) -- men
    ALDRI heve den selv, det ville vært en privilegie-eskaleringsvei.
    Avviser med 409 LAST_OWNER hvis endringen ville latt organisasjonen stå
    igjen uten noen eier."""
    async with org_connection(organization_id) as conn, translate_db_errors():
        target = await conn.fetchrow(
            """
            SELECT id::text AS id, user_id::text AS user_id, role
            FROM organization_membership WHERE id = $1 AND organization_id = $2
            FOR UPDATE
            """,
            membership_id,
            organization_id,
        )
        if target is None:
            raise app_error(404, "NOT_FOUND", "Medlemskapet finnes ikke.")

        is_self = target["user_id"] == user.user_id
        if is_self:
            if _ROLE_RANK[body.role] >= _ROLE_RANK[target["role"]]:
                raise app_error(
                    403, "NOT_ORG_OWNER", "Du kan bare senke din egen rolle selv, ikke heve den."
                )
        else:
            caller_role = await _caller_role(conn, organization_id, user.user_id)
            if caller_role != "owner":
                raise app_error(403, "NOT_ORG_OWNER", "Kun organisasjonseiere kan endre andres rolle.")

        if target["role"] == "owner" and body.role != "owner":
            if await _owner_count_locked(conn, organization_id) <= 1:
                raise app_error(409, "LAST_OWNER", "Organisasjonen må ha minst én eier.")

        row = await conn.fetchrow(
            """
            WITH updated AS (
                UPDATE organization_membership SET role = $1 WHERE id = $2
                RETURNING id, user_id, role
            )
            SELECT updated.id::text AS id, updated.user_id::text AS user_id, updated.role,
                   u.email::text AS email, u.display_name
            FROM updated JOIN app_user u ON u.id = updated.user_id
            """,
            body.role,
            membership_id,
        )
    return MembershipOut(**dict(row))


@router.delete("/orgs/{organization_id}/memberships/{membership_id}", status_code=204)
async def delete_membership(
    membership_id: str,
    organization_id: str = Depends(get_authorized_org),
    user: CurrentUser = Depends(get_current_user),
) -> None:
    """Eier kan fjerne ethvert medlem. Enhver bruker kan fjerne SEG SELV
    (forlate organisasjonen). Avviser med 409 LAST_OWNER hvis den siste
    eieren prøver å fjernes/forlate uten å ha overført eierskap først."""
    async with org_connection(organization_id) as conn:
        target = await conn.fetchrow(
            """
            SELECT user_id::text AS user_id, role
            FROM organization_membership WHERE id = $1 AND organization_id = $2
            FOR UPDATE
            """,
            membership_id,
            organization_id,
        )
        if target is None:
            raise app_error(404, "NOT_FOUND", "Medlemskapet finnes ikke.")

        is_self = target["user_id"] == user.user_id
        if not is_self:
            caller_role = await _caller_role(conn, organization_id, user.user_id)
            if caller_role != "owner":
                raise app_error(403, "NOT_ORG_OWNER", "Kun organisasjonseiere kan fjerne andre medlemmer.")

        if target["role"] == "owner":
            if await _owner_count_locked(conn, organization_id) <= 1:
                raise app_error(
                    409,
                    "LAST_OWNER",
                    "Organisasjonen må ha minst én eier -- overfør eierskap før du forlater.",
                )

        await conn.execute("DELETE FROM organization_membership WHERE id = $1", membership_id)


# ---------------------------------------------------------------------------
# Invitasjoner (ADR-022 Beslutning B) -- e-post-basert, ingen egen
# klikkbar aksept-lenke. Godtas automatisk ved neste innlogging med
# matchende e-post (app/routers/auth.py sin accept_pending_invitations_by_email).
# ---------------------------------------------------------------------------


class InvitationCreate(BaseModel):
    email: EmailStr
    role: str = Field(pattern="^(owner|admin|member)$")


class InvitationOut(BaseModel):
    id: str
    email: str
    role: str
    invited_by: str
    created_at: str
    expires_at: str


@router.post(
    "/orgs/{organization_id}/invitations", response_model=InvitationOut, status_code=201
)
async def create_invitation(
    body: InvitationCreate,
    organization_id: str = Depends(get_authorized_org),
    user: CurrentUser = Depends(get_current_user),
) -> InvitationOut:
    async with org_connection(organization_id) as conn, translate_db_errors():
        caller_role = await _caller_role(conn, organization_id, user.user_id)
        if caller_role not in ("owner", "admin"):
            raise app_error(403, "NOT_ORG_OWNER", "Kun eiere og administratorer kan invitere.")
        # Minste-privilegium (ADR-022 Beslutning B): en admin kan gi noen
        # tilgang som medlem, men ALDRI gi noen eier-/adminrettigheter --
        # det ville vært en privilegie-eskaleringsvei.
        if caller_role == "admin" and body.role != "member":
            raise app_error(
                403, "NOT_ORG_OWNER", "Administratorer kan kun invitere som medlem, ikke som eier/admin."
            )

        org_name = await conn.fetchval("SELECT name FROM organization WHERE id = $1", organization_id)
        inviter = await conn.fetchrow(
            "SELECT display_name, preferred_locale FROM app_user WHERE id = $1", user.user_id
        )

        row = await conn.fetchrow(
            """
            INSERT INTO organization_invitation
                (organization_id, email, role, invited_by, expires_at)
            VALUES ($1, $2, $3, $4, now() + interval '14 days')
            RETURNING id::text, email::text, role, invited_by::text AS invited_by,
                      created_at::text, expires_at::text
            """,
            organization_id,
            body.email.lower(),
            body.role,
            user.user_id,
        )

    if settings.SMTP_CONFIGURED:
        try:
            await send_organization_invitation_email(
                body.email.lower(), org_name, body.role, inviter["display_name"], inviter["preferred_locale"]
            )
        except Exception:
            # Se app/email.py sitt mønster -- driftsfeil i utsendingen skal
            # aldri hindre selve invitasjonen fra å bli opprettet.
            traceback.print_exc()
    elif settings.DEV_LOG_MAGIC_LINKS:
        print(
            f"[DEV] Invitasjon til {body.email.lower()} for {org_name} som {body.role}", flush=True
        )

    return InvitationOut(**dict(row))


@router.get("/orgs/{organization_id}/invitations", response_model=list[InvitationOut])
async def list_invitations(organization_id: str = Depends(get_authorized_org)) -> list[InvitationOut]:
    async with org_connection(organization_id) as conn:
        rows = await conn.fetch(
            """
            SELECT id::text, email::text, role, invited_by::text AS invited_by,
                   created_at::text, expires_at::text
            FROM organization_invitation
            WHERE organization_id = $1 AND consumed_at IS NULL AND expires_at > now()
            ORDER BY created_at DESC
            """,
            organization_id,
        )
    return [InvitationOut(**dict(r)) for r in rows]


@router.delete("/orgs/{organization_id}/invitations/{invitation_id}", status_code=204)
async def revoke_invitation(
    invitation_id: str,
    organization_id: str = Depends(get_authorized_org),
    user: CurrentUser = Depends(get_current_user),
) -> None:
    async with org_connection(organization_id) as conn:
        invitation = await conn.fetchrow(
            "SELECT role FROM organization_invitation WHERE id = $1 AND organization_id = $2",
            invitation_id,
            organization_id,
        )
        if invitation is None:
            raise app_error(404, "NOT_FOUND", "Invitasjonen finnes ikke.")

        caller_role = await _caller_role(conn, organization_id, user.user_id)
        allowed = caller_role == "owner" or (caller_role == "admin" and invitation["role"] == "member")
        if not allowed:
            raise app_error(403, "NOT_ORG_OWNER", "Du har ikke rettigheter til å oppheve denne invitasjonen.")

        await conn.execute("DELETE FROM organization_invitation WHERE id = $1", invitation_id)


# ---------------------------------------------------------------------------
# Superadmin (ADR-022 Beslutning D) -- kun for kontoer med is_super_admin
# satt DIREKTE i databasen (aldri via noe API). Kan sette medlemskap på
# ENHVER organisasjon, uavhengig av eget medlemskap -- bevisst avgrenset
# til nøyaktig dette (ikke generell tilgang til andres turnering-/spillerdata).
# ---------------------------------------------------------------------------


class SuperadminMembershipSet(BaseModel):
    email: EmailStr
    role: str = Field(pattern="^(owner|admin|member)$")


@router.post("/superadmin/orgs/{organization_id}/memberships", response_model=MembershipOut)
async def superadmin_set_membership(
    organization_id: str,
    body: SuperadminMembershipSet,
    _admin: CurrentUser = Depends(get_superadmin_user),
) -> MembershipOut:
    async with org_connection(organization_id) as conn, translate_db_errors():
        target_user = await conn.fetchrow(
            "SELECT id::text AS id, display_name FROM app_user WHERE email = $1", body.email.lower()
        )
        if target_user is None:
            raise app_error(
                404,
                "NOT_FOUND",
                "Fant ingen bruker med denne e-posten -- personen må ha logget inn minst én gang først.",
            )
        row = await conn.fetchrow(
            """
            INSERT INTO organization_membership (organization_id, user_id, role)
            VALUES ($1, $2, $3)
            ON CONFLICT (organization_id, user_id) DO UPDATE SET role = EXCLUDED.role
            RETURNING id::text, user_id::text, role
            """,
            organization_id,
            target_user["id"],
            body.role,
        )
    return MembershipOut(
        id=row["id"],
        user_id=row["user_id"],
        role=row["role"],
        email=body.email.lower(),
        display_name=target_user["display_name"],
    )
