"""Authentication endpoints."""
from secrets import token_urlsafe
from typing import Literal, Optional

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError, jwt
from pydantic import BaseModel

from ..config import settings
from ..database import get_session
from ..models.admin_user import AdminUser
from ..security import create_access_token, hash_password, verify_password
from ..utils import log_action

router = APIRouter()


class TokenResponse(BaseModel):
    access_token: str
    token_type: str = "bearer"


class OIDCLoginRequest(BaseModel):
    """Payload emitted by the admin IdP."""

    id_token: str


def _log_failed_login(
    session,
    attempted_email: str,
    reason: Literal["not_found", "invalid_password", "oidc_forbidden", "invalid_token", "missing_email"],
) -> None:
    """Persist an audit trail entry for failed login attempts."""
    log_action(session, attempted_email, "admin_login_failed", metadata={"reason": reason})
    session.commit()


def _ensure_oidc_configured() -> None:
    required = [
        settings.admin_oidc_client_secret,
        settings.admin_oidc_audience,
        settings.admin_oidc_issuer,
    ]
    if not all(required):
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="SSO non configuré")


def _normalise_groups(raw_groups: Optional[object]) -> set[str]:
    if raw_groups is None:
        return set()
    if isinstance(raw_groups, str):
        return {raw_groups.lower()}
    if isinstance(raw_groups, (list, tuple, set)):
        return {str(item).lower() for item in raw_groups}
    return set()


@router.post("/login", response_model=TokenResponse)
async def login(form_data: OAuth2PasswordRequestForm = Depends(), session=Depends(get_session)) -> TokenResponse:
    admin = session.query(AdminUser).filter(AdminUser.email == form_data.username).first()
    if not admin:
        _log_failed_login(session, form_data.username, "not_found")
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Identifiants invalides")
    if not verify_password(form_data.password, admin.hashed_password):
        _log_failed_login(session, admin.email, "invalid_password")
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Identifiants invalides")

    token = create_access_token(admin.email)
    log_action(session, admin.email, "admin_login")
    session.commit()
    return TokenResponse(access_token=token)


@router.post("/oidc/login", response_model=TokenResponse)
async def oidc_login(payload: OIDCLoginRequest, session=Depends(get_session)) -> TokenResponse:
    _ensure_oidc_configured()
    try:
        claims = jwt.decode(
            payload.id_token,
            settings.admin_oidc_client_secret,
            algorithms=["HS256"],
            audience=settings.admin_oidc_audience,
            issuer=settings.admin_oidc_issuer,
        )
    except JWTError as exc:
        _log_failed_login(session, "oidc", "invalid_token")
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token OIDC invalide") from exc

    email = claims.get("email")
    if not email:
        _log_failed_login(session, "oidc", "missing_email")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Revendication email absente")

    mfa_claim = settings.admin_oidc_mfa_claim or "mfa_authenticated"
    if not claims.get(mfa_claim):
        _log_failed_login(session, email, "oidc_forbidden")
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="MFA requis pour accéder au panel")

    if settings.admin_allowed_domains:
        domain = email.split("@")[-1].lower()
        allowed = {item.lower() for item in settings.admin_allowed_domains}
        if domain not in allowed:
            _log_failed_login(session, email, "oidc_forbidden")
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Domaine email non autorisé")

    required_groups = {grp.lower() for grp in settings.admin_oidc_allowed_groups}
    if required_groups:
        groups = _normalise_groups(claims.get("groups"))
        if not groups.intersection(required_groups):
            _log_failed_login(session, email, "oidc_forbidden")
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Groupe OIDC manquant")

    admin = session.query(AdminUser).filter(AdminUser.email == email).first()
    if not admin:
        admin = AdminUser(
            email=email,
            hashed_password=hash_password(token_urlsafe(32)),
            role=claims.get("role", "support"),
        )
        session.add(admin)
    else:
        new_role = claims.get("role")
        if new_role:
            admin.role = new_role

    token = create_access_token(admin.email)
    log_action(
        session,
        admin.email,
        "admin_login_oidc",
        metadata={
            "issuer": settings.admin_oidc_issuer,
            "groups": list(_normalise_groups(claims.get("groups"))),
        },
    )
    session.commit()
    session.refresh(admin)
    return TokenResponse(access_token=token)


@router.post("/bootstrap", response_model=TokenResponse)
async def bootstrap_admin(session=Depends(get_session)) -> TokenResponse:
    admin = session.query(AdminUser).first()
    if admin:
        raise HTTPException(status_code=400, detail="Admin déjà initialisé")

    admin = AdminUser(
        email="sysop@web-sentinel.com",
        hashed_password=hash_password("change-me"),
        role="super-admin",
    )
    session.add(admin)
    session.commit()

    token = create_access_token(admin.email)
    log_action(session, admin.email, "admin_login")
    session.commit()
    return TokenResponse(access_token=token)
