"""
Anträge auf Vereinsbeitritt: Nutzer stellt Antrag, Vereins-/Plattform-Admin nimmt an oder lehnt ab.
"""
from typing import Any, Dict, List, Optional

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

from club_tenancy import can_manage_club_org, is_platform_admin
from db import get_db, get_cursor, r2d
from tenant_context import TenantContext, get_tenant_context

router = APIRouter(prefix="/api", tags=["club_join_requests"])

_ALLOWED_MEMBER_ROLES = frozenset({"club_admin", "trainer", "division_lead", "content_editor"})


def _normalize_roles(raw: List[str]) -> List[str]:
    out: List[str] = []
    seen = set()
    for r in raw:
        if not isinstance(r, str):
            raise HTTPException(status_code=400, detail="Rollen müssen Strings sein")
        code = r.strip().lower()
        if not code or code not in _ALLOWED_MEMBER_ROLES:
            raise HTTPException(status_code=400, detail=f"Unbekannte Rolle: {code}")
        if code not in seen:
            seen.add(code)
            out.append(code)
    return out


def _club_active(cur, club_id: int) -> bool:
    cur.execute("SELECT 1 FROM clubs WHERE id = %s AND status = 'active'", (club_id,))
    return cur.fetchone() is not None


def _assert_manage_club(cur, tenant: TenantContext, club_id: int) -> None:
    pid = tenant.profile_id
    role = tenant.global_role
    if not can_manage_club_org(cur, pid, club_id, role):
        raise HTTPException(status_code=403, detail="Keine Berechtigung für Mitglieder-Verwaltung in diesem Verein")


def _is_active_member(cur, profile_id: int, club_id: int) -> bool:
    cur.execute(
        """
        SELECT 1 FROM club_members
        WHERE profile_id = %s AND club_id = %s AND status = 'active'
        LIMIT 1
        """,
        (profile_id, club_id),
    )
    return cur.fetchone() is not None


def _upsert_active_member_with_roles(cur, club_id: int, profile_id: int, roles: List[str]) -> None:
    roles_n = _normalize_roles(roles)
    if not roles_n:
        raise HTTPException(status_code=400, detail="Mindestens eine Rolle angeben")
    cur.execute(
        """
        INSERT INTO club_members (profile_id, club_id, status)
        VALUES (%s, %s, 'active')
        ON CONFLICT (profile_id, club_id)
        DO UPDATE SET status = 'active', updated_at = NOW()
        RETURNING id
        """,
        (profile_id, club_id),
    )
    cm_id = cur.fetchone()["id"]
    cur.execute("DELETE FROM club_member_roles WHERE club_member_id = %s", (cm_id,))
    for rc in roles_n:
        cur.execute(
            """
            INSERT INTO club_member_roles (club_member_id, role_code)
            VALUES (%s, %s)
            ON CONFLICT (club_member_id, role_code) DO NOTHING
            """,
            (cm_id, rc),
        )


class JoinRequestCreate(BaseModel):
    club_id: int = Field(..., ge=1)
    message: Optional[str] = Field(None, max_length=2000)


class JoinRequestAccept(BaseModel):
    roles: List[str] = Field(default_factory=lambda: ["trainer"])


def _response_one(cur, req_id: int, viewer_profile_id: int) -> Dict[str, Any]:
    cur.execute(
        """
        SELECT r.*, c.name AS club_name, c.abbreviation AS club_abbreviation
        FROM club_membership_requests r
        INNER JOIN clubs c ON c.id = r.club_id
        WHERE r.id = %s AND r.profile_id = %s
        """,
        (req_id, viewer_profile_id),
    )
    row = cur.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Antrag nicht gefunden")
    return r2d(row)


def _can_access_org_inbox(cur, profile_id: int, global_role: Optional[str]) -> bool:
    """Posteingang (Beitrittsanträge bearbeiten): Plattform-Admin oder Vereinsadmin in mind. einem Verein."""
    if is_platform_admin(global_role):
        return True
    cur.execute(
        """
        SELECT 1
        FROM club_members cm
        INNER JOIN club_member_roles r ON r.club_member_id = cm.id AND r.role_code = 'club_admin'
        WHERE cm.profile_id = %s AND cm.status = 'active'
        LIMIT 1
        """,
        (profile_id,),
    )
    return cur.fetchone() is not None


def _club_ids_manageable_by_user(cur, profile_id: int, global_role: Optional[str]) -> List[int]:
    """Club-IDs, für die der Nutzer Beitrittsanträge sehen darf."""
    if is_platform_admin(global_role):
        cur.execute("SELECT id FROM clubs WHERE status = 'active' ORDER BY name")
        return [int(r["id"]) for r in cur.fetchall()]
    cur.execute(
        """
        SELECT DISTINCT cm.club_id
        FROM club_members cm
        INNER JOIN club_member_roles r ON r.club_member_id = cm.id AND r.role_code = 'club_admin'
        WHERE cm.profile_id = %s AND cm.status = 'active'
        """,
        (profile_id,),
    )
    return [int(r["club_id"]) for r in cur.fetchall()]


@router.get("/me/inbox/join-requests")
def list_inbox_join_requests(tenant: TenantContext = Depends(get_tenant_context)):
    """
    Alle offenen Beitrittsanträge, die der Nutzer bearbeiten darf:
    Plattform-Admin: alle Vereine; sonst nur Vereine mit Rolle club_admin.
    """
    pid = tenant.profile_id
    role = tenant.global_role
    with get_db() as conn:
        cur = get_cursor(conn)
        if not _can_access_org_inbox(cur, pid, role):
            raise HTTPException(status_code=403, detail="Kein Zugriff auf den Organisations-Posteingang")
        club_ids = _club_ids_manageable_by_user(cur, pid, role)
        if not club_ids:
            return []
        cur.execute(
            """
            SELECT r.id, r.profile_id, r.club_id, r.status, r.message, r.created_at,
                   c.name AS club_name, c.abbreviation AS club_abbreviation,
                   p.name AS applicant_name, p.email AS applicant_email
            FROM club_membership_requests r
            INNER JOIN clubs c ON c.id = r.club_id
            INNER JOIN profiles p ON p.id = r.profile_id
            WHERE r.status = 'pending'
              AND r.club_id = ANY(%s)
            ORDER BY r.created_at ASC
            """,
            (club_ids,),
        )
        return [r2d(row) for row in cur.fetchall()]


@router.get("/me/club-join-requests")
def get_my_join_requests(tenant: TenantContext = Depends(get_tenant_context)):
    pid = tenant.profile_id
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """
            SELECT r.*, c.name AS club_name, c.abbreviation AS club_abbreviation
            FROM club_membership_requests r
            INNER JOIN clubs c ON c.id = r.club_id
            WHERE r.profile_id = %s
            ORDER BY r.created_at DESC
            LIMIT 100
            """,
            (pid,),
        )
        return [r2d(r) for r in cur.fetchall()]


@router.post("/me/club-join-requests", status_code=201)
def create_my_join_request(body: JoinRequestCreate, tenant: TenantContext = Depends(get_tenant_context)):
    """Antrag stellen (nicht möglich wenn bereits aktives Mitglied)."""
    pid = tenant.profile_id
    msg = (body.message or "").strip() or None
    cid = body.club_id

    with get_db() as conn:
        cur = get_cursor(conn)
        if not _club_active(cur, cid):
            raise HTTPException(status_code=404, detail="Verein nicht gefunden oder nicht aktiv")

        if _is_active_member(cur, pid, cid):
            raise HTTPException(status_code=400, detail="Du bist bereits Mitglied in diesem Verein")

        cur.execute(
            """
            SELECT id FROM club_membership_requests
            WHERE profile_id = %s AND club_id = %s AND status = 'pending'
            LIMIT 1
            """,
            (pid, cid),
        )
        if cur.fetchone():
            raise HTTPException(status_code=409, detail="Für diesen Verein liegt bereits ein offener Antrag vor")

        cur.execute(
            """
            INSERT INTO club_membership_requests (profile_id, club_id, status, message)
            VALUES (%s, %s, 'pending', %s)
            RETURNING id
            """,
            (pid, cid, msg),
        )
        rid = cur.fetchone()["id"]
        conn.commit()

    with get_db() as conn:
        cur = get_cursor(conn)
        return _response_one(cur, rid, pid)


@router.delete("/me/club-join-requests/{request_id}")
def withdraw_my_join_request(request_id: int, tenant: TenantContext = Depends(get_tenant_context)):
    pid = tenant.profile_id
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """
            UPDATE club_membership_requests
            SET status = 'withdrawn', updated_at = NOW()
            WHERE id = %s AND profile_id = %s AND status = 'pending'
            RETURNING id
            """,
            (request_id, pid),
        )
        if not cur.fetchone():
            raise HTTPException(status_code=404, detail="Offener Antrag nicht gefunden")
        conn.commit()
    return {"ok": True}


@router.get("/clubs/{club_id}/join-requests")
def list_club_join_requests(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
    """Offene Anträge für einen Verein (Vereins-/Plattform-Admin)."""
    with get_db() as conn:
        cur = get_cursor(conn)
        _assert_manage_club(cur, tenant, club_id)
        cur.execute(
            """
            SELECT r.*, p.email AS applicant_email, p.name AS applicant_name
            FROM club_membership_requests r
            INNER JOIN profiles p ON p.id = r.profile_id
            WHERE r.club_id = %s AND r.status = 'pending'
            ORDER BY r.created_at ASC
            """,
            (club_id,),
        )
        return [r2d(r) for r in cur.fetchall()]


@router.post("/clubs/{club_id}/join-requests/{request_id}/accept")
def accept_club_join_request(
    club_id: int,
    request_id: int,
    body: JoinRequestAccept,
    tenant: TenantContext = Depends(get_tenant_context),
):
    admin_pid = tenant.profile_id
    roles = _normalize_roles(body.roles)

    with get_db() as conn:
        cur = get_cursor(conn)
        _assert_manage_club(cur, tenant, club_id)

        cur.execute(
            """
            SELECT id, profile_id, status FROM club_membership_requests
            WHERE id = %s AND club_id = %s
            """,
            (request_id, club_id),
        )
        row = cur.fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Antrag nicht gefunden")
        if row["status"] != "pending":
            raise HTTPException(status_code=400, detail="Antrag ist nicht mehr offen")

        applicant_id = row["profile_id"]

        cur.execute(
            """
            UPDATE club_membership_requests
            SET status = 'accepted',
                decided_by_profile_id = %s,
                decided_at = NOW(),
                updated_at = NOW()
            WHERE id = %s AND club_id = %s AND status = 'pending'
            RETURNING id
            """,
            (admin_pid, request_id, club_id),
        )
        if not cur.fetchone():
            raise HTTPException(status_code=409, detail="Antrag konnte nicht angenommen werden")

        _upsert_active_member_with_roles(cur, club_id, applicant_id, roles)
        conn.commit()

    return {"ok": True, "profile_id": applicant_id, "club_id": club_id}


@router.post("/clubs/{club_id}/join-requests/{request_id}/reject")
def reject_club_join_request(club_id: int, request_id: int, tenant: TenantContext = Depends(get_tenant_context)):
    admin_pid = tenant.profile_id
    with get_db() as conn:
        cur = get_cursor(conn)
        _assert_manage_club(cur, tenant, club_id)

        cur.execute(
            """
            UPDATE club_membership_requests
            SET status = 'rejected',
                decided_by_profile_id = %s,
                decided_at = NOW(),
                updated_at = NOW()
            WHERE id = %s AND club_id = %s AND status = 'pending'
            RETURNING id
            """,
            (admin_pid, request_id, club_id),
        )
        if not cur.fetchone():
            raise HTTPException(status_code=404, detail="Offener Antrag nicht gefunden")
        conn.commit()
    return {"ok": True}
