"""
DTOs para módulo de autenticación
"""

from typing import Optional

from pydantic import BaseModel, EmailStr, Field


class LoginRequestDTO(BaseModel):
    """DTO para solicitud de login"""

    username: str = Field(..., description="Usuario o email")
    password: str = Field(..., min_length=6, description="Contraseña")
    # Opcional en el primer paso: si no se provee, el backend verifica credenciales
    # y devuelve la lista de tenants disponibles en lugar de tokens.
    tenant_slug: Optional[str] = Field(None, description="Slug del tenant")

    class Config:
        json_schema_extra = {
            "example": {
                "username": "usuario@ejemplo.com",
                "password": "password123",
                "tenant_slug": "empresa-abc",
            }
        }


class TokenResponseDTO(BaseModel):
    """DTO para respuesta de token"""

    access_token: str
    refresh_token: str
    token_type: str = "bearer"
    expires_in: int
    tenant: Optional["TenantInfoDTO"] = None
    tenant_id: Optional[int] = None
    tenant_slug: Optional[str] = None
    # Sesión local del CRM (patrón SIWEB) — presente solo con SESSION_STORE_ENABLED.
    # Es un JWT propio (HS256) que la app usa como bearer para el backend del CRM y
    # que sobrevive aunque el refresh del token KC contra el Hub falle. El access_token
    # de arriba sigue siendo el de Keycloak (para llamadas al Hub).
    session_token: Optional[str] = None
    session_id: Optional[str] = None

    class Config:
        json_schema_extra = {
            "example": {
                "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
                "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
                "token_type": "bearer",
                "expires_in": 3600,
            }
        }


class RefreshTokenRequestDTO(BaseModel):
    """DTO para solicitud de refresh token"""

    refresh_token: str = Field(..., description="Refresh token")
    # Sesión local actual del CRM (patrón SIWEB). Si se envía, el backend preserva el
    # inicio de sesión (cap absoluto) y puede re-emitirla como fallback cuando el
    # refresh del token KC contra el Hub falla ("Token is not active" del relay).
    session_token: Optional[str] = Field(None, description="Sesión local actual del CRM (opcional)")
    # session_id opaco de la sesión en valkey (guarda los tokens KC fuera del browser).
    session_id: Optional[str] = Field(None, description="ID de sesión en valkey (opcional)")


class UserInfoResponseDTO(BaseModel):
    """DTO para información de usuario"""

    sub: str
    email: Optional[str] = None
    name: Optional[str] = None
    preferred_username: Optional[str] = None
    tenant_id: Optional[int] = None
    tenant_slug: Optional[str] = None
    avatar_url: Optional[str] = None
    roles: list[str] = []
    permissions: list[str] = []

    class Config:
        json_schema_extra = {
            "example": {
                "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                "email": "usuario@ejemplo.com",
                "name": "Juan Pérez",
                "preferred_username": "jperez",
                "tenant_id": 1,
                "roles": ["user", "admin"],
                "permissions": ["cat_ports.view", "cat_ports.create"]
            }
        }


class LogoutRequestDTO(BaseModel):
    """DTO para solicitud de logout"""

    refresh_token: str = Field(..., description="Refresh token para invalidar")
    username: Optional[str] = Field(None, description="Nombre de usuario para auditoría")


class RegisterRequestDTO(BaseModel):
    """DTO para solicitud de registro"""

    username: str = Field(
        ..., min_length=3, max_length=50, description="Nombre de usuario"
    )
    email: EmailStr = Field(..., description="Email del usuario")
    password: str = Field(..., min_length=8, description="Contraseña")
    first_name: str = Field(..., min_length=2, max_length=50, description="Nombre")
    last_name: str = Field(..., min_length=2, max_length=50, description="Apellido")
    tenant_slug: str = Field(..., description="Slug del tenant")
    invite_token: Optional[str] = Field(None, description="Token de invitación local (opcional)")

    class Config:
        json_schema_extra = {
            "example": {
                "username": "jperez",
                "email": "jperez@ejemplo.com",
                "password": "MiPassword123!",
                "first_name": "Juan",
                "last_name": "Pérez",
                "tenant_slug": "empresa-abc",
            }
        }


class RegisterResponseDTO(BaseModel):
    """DTO para respuesta de registro"""

    user_id: str
    username: str
    email: str
    message: str

    class Config:
        json_schema_extra = {
            "example": {
                "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                "username": "jperez",
                "email": "jperez@ejemplo.com",
                "message": "User registered successfully",
            }
        }


class ExchangeCodeRequestDTO(BaseModel):
    """DTO para intercambiar authorization code por tokens (OAuth2 flow)"""

    code: str = Field(..., description="Authorization code de OAuth2")
    redirect_uri: str = Field(..., description="Redirect URI usado en la autorización")
    tenant_slug: Optional[str] = Field(None, description="Slug del tenant (opcional)")

    class Config:
        json_schema_extra = {
            "example": {
                "code": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Ii...",
                "redirect_uri": "http://localhost:5173/auth/callback",
                "tenant_slug": "empresa-abc",
            }
        }


class SetCookieRequestDTO(BaseModel):
    """DTO para establecer cookies de autenticación"""

    access_token: str = Field(..., description="Access token JWT")
    refresh_token: str = Field(..., description="Refresh token JWT")


class SwitchTenantRequestDTO(BaseModel):
    """DTO para cambiar de tenant estando autenticado"""

    tenant_slug: str = Field(..., description="Slug del tenant destino")
    refresh_token: str = Field(..., description="Refresh token actual para emitir nuevos tokens")


class DiscoverTenantsRequestDTO(BaseModel):
    """DTO para descubrir los tenants de un usuario sin necesidad de indicarlo manualmente"""

    username: str = Field(..., description="Nombre de usuario o email")

    class Config:
        json_schema_extra = {
            "example": {
                "username": "jperez",
            }
        }


class TenantInfoDTO(BaseModel):
    """Información básica de un tenant para mostrar en el selector de login"""

    id: int
    name: str
    slug: str

    class Config:
        from_attributes = True


class DiscoverTenantsResponseDTO(BaseModel):
    """Respuesta con los tenants disponibles para un usuario"""

    tenants: list[TenantInfoDTO]


class LoginChoiceResponseDTO(BaseModel):
    """
    Respuesta del login cuando el usuario pertenece a varios tenants.
    Las credenciales ya fueron verificadas; el cliente debe re-enviar con tenant_slug.
    """

    status: str = "choose_tenant"
    tenants: list[TenantInfoDTO]


class SSOExchangeRequestDTO(BaseModel):
    """DTO para canjear el relay token por KC tokens."""

    relay_token: str = Field(..., description="Relay token recibido en la URL")
