"""
Vereins-Mitgliedschaften und Rollen (ohne UI nutzbar für Admin/Automatisierung).

Berechtigung: Plattform-Admin (admin/superadmin) oder Vereinsadmin (club_admin) im Zielverein.
"""
from typing import Any, Dict, List, Optional

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

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

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

_ALLOWED_ROLES = frozenset({"club_admin", "trainer", "division_lead", "content_editor"})
_ALLOWED_STATUS = frozenset({"active", "inactive"})


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:
            continue
        if code not in _ALLOWED_ROLES:
            raise HTTPException(
                status_code=400,
                detail=f"Unbekannte Rolle: {code}. Erlaubt: {', '.join(sorted(_ALLOWED_ROLES))}",
            )
        if code not in seen:
            seen.add(code)
            out.append(code)
    return out


def _assert_manage(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 zur Mitglieder-Verwaltung in diesem Verein")


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


def _count_other_active_club_admins(cur, club_id: int, exclude_profile_id: int) -> int:
    """Aktive Vereinsadmins im Verein, außer exclude_profile_id."""
    cur.execute(
        """
        SELECT COUNT(DISTINCT cm.profile_id)::int AS n
        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.club_id = %s AND cm.status = 'active' AND cm.profile_id <> %s
        """,
        (club_id, exclude_profile_id),
    )
    row = cur.fetchone()
    try:
        return int(row["n"]) if row is not None else 0
    except (KeyError, TypeError, ValueError):
        return 0


def _member_is_active_club_admin(cur, club_id: int, profile_id: int) -> bool:
    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.club_id = %s AND cm.profile_id = %s AND cm.status = 'active'
        LIMIT 1
        """,
        (club_id, profile_id),
    )
    return cur.fetchone() is not None


_LAST_CLUB_ADMIN_MSG = (
    "Mindestens ein aktiver Vereinsadmin muss im Verein verbleiben. "
    "Weise die Rolle zuerst einem anderen Mitglied zu oder aktiviere einen anderen Vereinsadmin."
)


class ClubMemberUpsert(BaseModel):
    profile_id: int = Field(..., ge=1)
    roles: List[str] = Field(default_factory=list, description="Mindestens eine Vereinsrolle")


class ClubMemberPatch(BaseModel):
    roles: Optional[List[str]] = None
    status: Optional[str] = Field(None, description="active oder inactive")


@router.get("/clubs/{club_id}/members")
def list_club_members(
    club_id: int,
    include_inactive: bool = Query(False),
    tenant: TenantContext = Depends(get_tenant_context),
):
    """Alle Mitglieder eines Vereins mit Rollen (nur Vereins-/Plattform-Admin)."""
    with get_db() as conn:
        cur = get_cursor(conn)
        if not _club_exists(cur, club_id):
            raise HTTPException(status_code=404, detail="Verein nicht gefunden")
        _assert_manage(cur, tenant, club_id)

        status_clause = "" if include_inactive else "AND cm.status = 'active'"
        cur.execute(
            f"""
            SELECT cm.id AS membership_id, cm.profile_id, cm.status, cm.created_at, cm.updated_at,
                   p.email, p.name, COALESCE(p.email_verified, false) AS email_verified,
                   lower(trim(COALESCE(p.role, 'user'))) AS portal_role,
                   COALESCE(
                       ARRAY_AGG(DISTINCT r.role_code) FILTER (WHERE r.role_code IS NOT NULL),
                       ARRAY[]::varchar[]
                   ) AS roles
            FROM club_members cm
            INNER JOIN profiles p ON p.id = cm.profile_id
            LEFT JOIN club_member_roles r ON r.club_member_id = cm.id
            WHERE cm.club_id = %s {status_clause}
            GROUP BY cm.id, cm.profile_id, cm.status, cm.created_at, cm.updated_at, p.email, p.name, p.email_verified, p.role
            ORDER BY p.name NULLS LAST, p.email
            """,
            (club_id,),
        )
        rows = []
        for row in cur.fetchall():
            d = r2d(row)
            roles = d.get("roles") or []
            if hasattr(roles, "tolist"):
                roles = roles.tolist()
            d["roles"] = list(roles)
            rows.append(d)
        return rows


@router.post("/clubs/{club_id}/members", status_code=201)
def upsert_club_member(
    club_id: int,
    body: ClubMemberUpsert,
    tenant: TenantContext = Depends(get_tenant_context),
):
    """Mitglied anlegen oder aktivieren; Rollen werden vollständig ersetzt."""
    roles = _normalize_roles(body.roles)
    if not roles:
        raise HTTPException(status_code=400, detail="Mindestens eine Rolle angeben")

    with get_db() as conn:
        cur = get_cursor(conn)
        if not _club_exists(cur, club_id):
            raise HTTPException(status_code=404, detail="Verein nicht gefunden")
        _assert_manage(cur, tenant, club_id)

        cur.execute("SELECT id FROM profiles WHERE id = %s", (body.profile_id,))
        if not cur.fetchone():
            raise HTTPException(status_code=404, detail="Profil nicht gefunden")

        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
            """,
            (body.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:
            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),
            )
        conn.commit()
        return _one_member(cur, club_id, body.profile_id)


def _one_member(cur, club_id: int, profile_id: int) -> Dict[str, Any]:
    cur.execute(
        """
        SELECT cm.id AS membership_id, cm.profile_id, cm.status, cm.created_at, cm.updated_at,
               p.email, p.name, COALESCE(p.email_verified, false) AS email_verified,
               lower(trim(COALESCE(p.role, 'user'))) AS portal_role,
               COALESCE(
                   ARRAY_AGG(DISTINCT r.role_code) FILTER (WHERE r.role_code IS NOT NULL),
                   ARRAY[]::varchar[]
               ) AS roles
        FROM club_members cm
        INNER JOIN profiles p ON p.id = cm.profile_id
        LEFT JOIN club_member_roles r ON r.club_member_id = cm.id
        WHERE cm.club_id = %s AND cm.profile_id = %s
        GROUP BY cm.id, cm.profile_id, cm.status, cm.created_at, cm.updated_at, p.email, p.name, p.email_verified, p.role
        """,
        (club_id, profile_id),
    )
    row = cur.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Mitgliedschaft nicht gefunden")
    d = r2d(row)
    roles = d.get("roles") or []
    if hasattr(roles, "tolist"):
        roles = roles.tolist()
    d["roles"] = list(roles)
    return d


@router.get("/clubs/{club_id}/members/{profile_id}")
def get_club_member(
    club_id: int,
    profile_id: int,
    tenant: TenantContext = Depends(get_tenant_context),
):
    with get_db() as conn:
        cur = get_cursor(conn)
        if not _club_exists(cur, club_id):
            raise HTTPException(status_code=404, detail="Verein nicht gefunden")
        _assert_manage(cur, tenant, club_id)
        return _one_member(cur, club_id, profile_id)


@router.put("/clubs/{club_id}/members/{profile_id}")
def update_club_member(
    club_id: int,
    profile_id: int,
    body: ClubMemberPatch,
    tenant: TenantContext = Depends(get_tenant_context),
):
    """Rollen ersetzen und/oder Status setzen."""
    with get_db() as conn:
        cur = get_cursor(conn)
        if not _club_exists(cur, club_id):
            raise HTTPException(status_code=404, detail="Verein nicht gefunden")
        _assert_manage(cur, tenant, club_id)

        cur.execute(
            "SELECT id FROM club_members WHERE club_id = %s AND profile_id = %s",
            (club_id, profile_id),
        )
        row = cur.fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Mitgliedschaft nicht gefunden")
        cm_id = row["id"]

        if body.roles is None and body.status is None:
            return _one_member(cur, club_id, profile_id)

        cur.execute(
            """
            SELECT cm.status,
                   COALESCE(
                       ARRAY_AGG(r.role_code) FILTER (WHERE r.role_code IS NOT NULL),
                       ARRAY[]::varchar[]
                   ) AS roles
            FROM club_members cm
            LEFT JOIN club_member_roles r ON r.club_member_id = cm.id
            WHERE cm.id = %s
            GROUP BY cm.status
            """,
            (cm_id,),
        )
        cur_row = cur.fetchone()
        current_status = (cur_row["status"] or "active").strip().lower() if cur_row else "active"
        cr = cur_row.get("roles") if cur_row else []
        if hasattr(cr, "tolist"):
            cr = cr.tolist()
        current_roles = list(cr)

        new_status = body.status.strip().lower() if body.status is not None else current_status
        if body.status is not None and new_status not in _ALLOWED_STATUS:
            raise HTTPException(status_code=400, detail="status muss active oder inactive sein")

        new_roles = _normalize_roles(body.roles) if body.roles is not None else current_roles
        if not new_roles:
            raise HTTPException(status_code=400, detail="Mindestens eine Rolle angeben")

        effective_admin = new_status == "active" and "club_admin" in set(new_roles)
        if not effective_admin:
            others = _count_other_active_club_admins(cur, club_id, profile_id)
            if others < 1:
                raise HTTPException(status_code=400, detail=_LAST_CLUB_ADMIN_MSG)

        if body.status is not None:
            cur.execute(
                "UPDATE club_members SET status = %s, updated_at = NOW() WHERE id = %s",
                (new_status, cm_id),
            )

        if body.roles is not None:
            cur.execute("DELETE FROM club_member_roles WHERE club_member_id = %s", (cm_id,))
            for rc in new_roles:
                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),
                )

        conn.commit()
        return _one_member(cur, club_id, profile_id)


@router.delete("/clubs/{club_id}/members/{profile_id}")
def delete_club_member(
    club_id: int,
    profile_id: int,
    tenant: TenantContext = Depends(get_tenant_context),
):
    """Mitgliedschaft löschen (Rollen per CASCADE)."""
    with get_db() as conn:
        cur = get_cursor(conn)
        if not _club_exists(cur, club_id):
            raise HTTPException(status_code=404, detail="Verein nicht gefunden")
        _assert_manage(cur, tenant, club_id)

        if _member_is_active_club_admin(cur, club_id, profile_id):
            if _count_other_active_club_admins(cur, club_id, profile_id) < 1:
                raise HTTPException(status_code=400, detail=_LAST_CLUB_ADMIN_MSG)

        cur.execute(
            "DELETE FROM club_members WHERE club_id = %s AND profile_id = %s RETURNING id",
            (club_id, profile_id),
        )
        if not cur.fetchone():
            raise HTTPException(status_code=404, detail="Mitgliedschaft nicht gefunden")
        conn.commit()
    return {"ok": True}
