"""
Endpoints API para autenticación
"""

from core.database import get_core_db
from core.security import get_current_user
from fastapi import APIRouter, Depends, HTTPException, Query, Response, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session

from .dto import (
    ExchangeCodeRequestDTO,
    LoginChoiceResponseDTO,
    LoginRequestDTO,
    LogoutRequestDTO,
    RefreshTokenRequestDTO,
    RegisterRequestDTO,
    RegisterResponseDTO,
    SetCookieRequestDTO,
    SSOExchangeRequestDTO,
    SwitchTenantRequestDTO,
    TokenResponseDTO,
    UserInfoResponseDTO,
)
from .service import AuthService

router = APIRouter(prefix="/auth", tags=["Authentication"])
security = HTTPBearer()


@router.get("/register/check")
async def check_register(
    invite_token: str = Query(..., description="Token de invitación"),
    tenant_slug: str = Query(..., description="Slug del tenant"),
    email: str = Query(..., description="Email del usuario invitado"),
    db: Session = Depends(get_core_db),
):
    """
    Valida un token de invitación y verifica si el email ya existe en Keycloak.
    No consume el token. Responde con user_exists y datos básicos del usuario si ya existe.
    """
    from api.v1.modules.core.invites.service import InviteService
    import httpx
    from core.config import settings

    invite_service = InviteService(db)
    # Valida token (lanza 403 si es inválido)
    invite_result = invite_service.validate(invite_token, tenant_slug, email)

    # Intentar verificar si el email ya existe en el Hub usando service account
    user_exists = False
    user_info: dict = {}
    if settings.HUB_ADMIN_EMAIL and settings.HUB_ADMIN_PASSWORD:
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                # Login con service account
                login_resp = await client.post(
                    f"{settings.HUB_URL}api/v1/auth/login",
                    json={"username": settings.HUB_ADMIN_EMAIL, "password": settings.HUB_ADMIN_PASSWORD},
                )
                if login_resp.status_code == 200:
                    svc_token = login_resp.json().get("access_token", "")
                    if svc_token:
                        # Buscar admin por email
                        admins_resp = await client.get(
                            f"{settings.HUB_URL}api/v1/hub/admins",
                            params={"email": email},
                            headers={"Authorization": f"Bearer {svc_token}"},
                        )
                        if admins_resp.status_code == 200:
                            admins = admins_resp.json()
                            if isinstance(admins, list):
                                matches = [a for a in admins if a.get("email", "").lower() == email.lower()]
                            elif isinstance(admins, dict) and "items" in admins:
                                matches = [a for a in admins["items"] if a.get("email", "").lower() == email.lower()]
                            else:
                                matches = []
                            if matches:
                                user_exists = True
                                a = matches[0]
                                user_info = {
                                    "username": a.get("username", ""),
                                    "first_name": a.get("first_name", ""),
                                    "last_name": a.get("last_name", ""),
                                }
        except Exception as exc:
            import logging
            logging.getLogger(__name__).warning("register/check Hub lookup failed: %s", exc)

    return {
        "email": invite_result.email,
        "role": invite_result.role,
        "user_exists": user_exists,
        **user_info,
    }


@router.post("/register", response_model=RegisterResponseDTO, status_code=201)
async def register(
    register_data: RegisterRequestDTO, db: Session = Depends(get_core_db)
):
    """
    Registra un nuevo usuario en Keycloak

    El usuario debe proporcionar:
    - username: Nombre de usuario único
    - email: Email único
    - password: Contraseña (mínimo 8 caracteres)
    - first_name: Nombre
    - last_name: Apellido
    - tenant_slug: Slug del tenant al que pertenece

    El usuario se crea automáticamente en Keycloak con:
    - Cuenta habilitada
    - Rol 'user' asignado por defecto
    - Atributos de tenant
    """
    service = AuthService(db)
    return await service.register(register_data)


@router.post("/login", response_model=None)
async def login(
    login_data: LoginRequestDTO, 
    request: Request, # Inject Request
    db: Session = Depends(get_core_db)
):
    """
    Autentica usuario con Keycloak y retorna tokens JWT
    
    El usuario debe proporcionar:
    - username: Usuario o email
    - password: Contraseña
    - tenant_slug: Slug del tenant al que pertenece
    """
    service = AuthService(db)
    import logging
    logger = logging.getLogger(__name__)
    return await service.login(
        login_data=login_data,
        ip_address=request.client.host,
        user_agent=request.headers.get("user-agent")
    )


@router.post("/switch-tenant", response_model=TokenResponseDTO)
async def switch_tenant(
    data: SwitchTenantRequestDTO,
    db: Session = Depends(get_core_db),
    credentials: HTTPAuthorizationCredentials = Depends(security),
):
    """
    Cambia el tenant activo de un usuario ya autenticado y retorna nuevos tokens JWT.

    Requiere:
    - Authorization: Bearer <access_token>  (para identificar al usuario)
    - Body: { tenant_slug, refresh_token }
    """
    service = AuthService(db)
    # Obtener info del usuario desde el access token actual
    user_info = await service.get_user_info(credentials.credentials)

    keycloak_user_id = user_info.sub
    # El realm se puede inferir del token; usamos el campo tenant_id para buscar el realm actual,
    # pero lo más directo es dejar que Keycloak lo resuelva usando la config global.
    # Todos los tenants comparten el mismo realm en esta arquitectura.
    from api.v1.modules.core.tenants.models import Tenant
    from core.database import get_core_db as _gcdb
    # Obtener el realm del tenant destino (o default)
    tenant = db.query(Tenant).filter(Tenant.slug == data.tenant_slug, Tenant.is_active).first()
    if not tenant:
        raise HTTPException(status_code=403, detail="Access denied")

    return await service.switch_tenant(
        keycloak_user_id=keycloak_user_id,
        keycloak_realm=tenant.keycloak_realm,
        tenant_slug=data.tenant_slug,
        refresh_token=data.refresh_token,
    )


@router.post("/refresh", response_model=TokenResponseDTO)
async def refresh_token(
    refresh_data: RefreshTokenRequestDTO, db: Session = Depends(get_core_db)
):
    """
    Refresca el access token usando el refresh token
    """
    service = AuthService(db)
    return await service.refresh_token(refresh_data)


@router.get("/me", response_model=UserInfoResponseDTO)
async def get_current_user_info(
    credentials: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_core_db),
):
    """
    Obtiene información del usuario actual desde el token
    """
    service = AuthService(db)
    return await service.get_user_info(credentials.credentials)


@router.post("/lazy-link", status_code=200)
async def lazy_link(
    credentials: HTTPAuthorizationCredentials = Depends(security),
    db: Session = Depends(get_core_db),
):
    """
    Vincula un invite pendiente al usuario autenticado (lazy-link).
    Se llama después de un SSO login desde el workspace para crear el UserTenant
    si hay un invite_token pendiente para el email del usuario.
    """
    service = AuthService(db)
    try:
        await service._link_pending_invite(
            credentials.credentials,  # username_or_email = token (fallback)
            access_token=credentials.credentials,
        )
    except Exception:
        pass
    return {"ok": True}


@router.post("/logout")
async def logout(
    logout_data: LogoutRequestDTO,
    request: Request, # Inject request for IP/User-Agent
    db: Session = Depends(get_core_db),
    # Make current_user optional to avoid 401 on expired tokens
    # We will try to use it if available, otherwise use DTO
    # Note: Depends(get_current_user) raises HTTPException if invalid, so we cannot make it optional easily without changing dependency.
    # Instead, we will rely on DTO username since user explicitly asked for this simplified flow.
    # But if we want to support both, we can't use strict dependency here if we expect it to work on expired tokens.
    # So we remove the strict dependency for now as per "simplified" request.
):
    """
    Cierra sesión invalidando el refresh token
    """
    # Extract info for logging (optional, but harmless to keep providing context if needed, 
    # but strictly speaking we can revert to just calling service)
    # The original file likely didn't have IP extraction here unless I added it. 
    # I'll keep it simple.

    service = AuthService(db)
    return await service.logout(logout_data)


@router.post("/exchange-code", response_model=TokenResponseDTO)
async def exchange_code(
    exchange_data: ExchangeCodeRequestDTO, db: Session = Depends(get_core_db)
):
    """
    Intercambia un authorization code de OAuth2 por tokens

    Este endpoint es útil cuando el frontend usa el flujo de autorización
    con proveedores externos (Microsoft, Google, etc.) a través de Keycloak.

    El código se obtiene después de que el usuario se autentica con el proveedor
    externo y Keycloak lo redirige al frontend con el código en los query params.
    """
    service = AuthService(db)
    return await service.exchange_code(exchange_data)


@router.post("/set-cookie")
async def set_cookie(
    cookie_data: SetCookieRequestDTO,
    response: Response,
    db: Session = Depends(get_core_db),
):
    """
    Establece cookies HttpOnly con los tokens de autenticación

    Este endpoint se llama desde el frontend después de una autenticación
    SSO exitosa para establecer las cookies de sesión necesarias para
    la validación server-side en los layouts protegidos.

    Las cookies se configuran como:
    - HttpOnly: No accesibles desde JavaScript (mayor seguridad)
    - Secure: Solo se envían por HTTPS (en producción)
    - SameSite=Lax: Protección contra CSRF
    - Max-Age: Tiempo de vida del token
    """
    # Validar que los tokens sean válidos decodificándolos
    service = AuthService(db)
    try:
        # Validar el access token
        user_info = await service.get_user_info(cookie_data.access_token)

        # Establecer las cookies
        # Access token cookie
        response.set_cookie(
            key="access_token",
            value=cookie_data.access_token,
            httponly=True,  # No accesible desde JavaScript
            secure=False,  # TODO: Cambiar a True en producción con HTTPS
            samesite="lax",  # Protección CSRF
            max_age=3600,  # 1 hora (ajustar según configuración del token)
            path="/",
        )

        # Refresh token cookie
        response.set_cookie(
            key="refresh_token",
            value=cookie_data.refresh_token,
            httponly=True,
            secure=False,  # TODO: Cambiar a True en producción con HTTPS
            samesite="lax",
            max_age=86400,  # 24 horas (ajustar según configuración del token)
            path="/",
        )

        return {
            "success": True,
            "message": "Cookies establecidas correctamente",
            "user": user_info,
        }

    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Error validando tokens: {str(e)}")


@router.post("/sso-exchange", response_model=TokenResponseDTO)
async def sso_exchange(
    body: SSOExchangeRequestDTO,
    response: Response,
    db: Session = Depends(get_core_db),
):
    """
    Canjea un relay token de un solo uso (generado por el Hub) por KC tokens.
    Llamado server-side desde la página /auth/sso del frontend de Anexo76.
    Establece cookies HttpOnly con los tokens y devuelve el resultado.
    """
    service = AuthService(db)
    tokens = await service.sso_exchange(body.relay_token)

    _is_prod = False  # TODO: leer de settings.ENVIRONMENT == "production"
    response.set_cookie(
        key="access_token",
        value=tokens.access_token,
        httponly=True,
        secure=_is_prod,
        samesite="lax",
        max_age=3600,
        path="/",
    )
    response.set_cookie(
        key="refresh_token",
        value=tokens.refresh_token,
        httponly=True,
        secure=_is_prod,
        samesite="lax",
        max_age=86400,
        path="/",
    )
    return tokens
