"""Authentication configuration specification.

This module defines the authentication and authorization configuration
for Prism-generated applications.
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field, field_validator, model_validator


class Role(BaseModel):
    """RBAC role definition.

    Roles group permissions together for easier access control management.
    """

    name: str = Field(..., description="Role name (e.g., 'admin', 'editor', 'user')")
    permissions: list[str] = Field(
        default_factory=list,
        description="List of permissions (e.g., ['posts.create', 'posts.delete'])",
    )
    description: str | None = Field(default=None, description="Human-readable role description")

    model_config = {"extra": "forbid"}


class APIKeyConfig(BaseModel):
    """API key authentication configuration.

    Simple bearer token authentication for backend services and APIs.

    Example:
        ```python
        from prism.spec import APIKeyConfig

        api_key = APIKeyConfig(
            header="Authorization",
            scheme="Bearer",
            env_var="API_KEY",
        )
        ```
    """

    header: str = Field(
        default="Authorization",
        description="HTTP header name for the API key (e.g., 'Authorization', 'X-API-Key')",
    )
    scheme: str = Field(
        default="Bearer",
        description="Authentication scheme (e.g., 'Bearer', 'ApiKey', or empty for raw key)",
    )
    env_var: str = Field(
        default="API_KEY",
        description="Environment variable name containing the API key",
    )
    allow_multiple_keys: bool = Field(
        default=False,
        description="Allow multiple valid API keys (comma-separated in env var)",
    )

    model_config = {"extra": "forbid"}


class AuthConfig(BaseModel):
    """Authentication configuration.

    Controls authentication and authorization behavior for the entire application.
    When enabled=False, no authentication code is generated (backward compatible).

    Supports two presets:
    - jwt: Full JWT-based auth with signup/login/refresh flows (default)
    - api_key: Simple API key authentication for backend services

    Example (JWT auth):
        ```python
        from prism.spec import AuthConfig, Role

        auth = AuthConfig(
            enabled=True,
            preset="jwt",
            secret_key="${JWT_SECRET}",
            access_token_expire_minutes=15,
            roles=[
                Role(name="admin", permissions=["*"]),
            ]
        )
        ```

    Example (API key auth):
        ```python
        from prism.spec import AuthConfig, APIKeyConfig

        auth = AuthConfig(
            enabled=True,
            preset="api_key",
            api_key=APIKeyConfig(
                header="Authorization",
                scheme="Bearer",
                env_var="API_KEY",
            ),
        )
        ```
    """

    # Core Settings
    enabled: bool = Field(
        default=False,
        description="Enable authentication system (opt-in for backward compatibility)",
    )
    preset: Literal["jwt", "api_key"] = Field(
        default="jwt",
        description="Authentication preset: 'jwt' for full auth flow, 'api_key' for simple API keys",
    )

    # API Key Configuration (only used when preset="api_key")
    api_key: APIKeyConfig = Field(
        default_factory=APIKeyConfig,
        description="API key configuration (only used when preset='api_key')",
    )

    # JWT Settings
    secret_key: str = Field(
        default="${JWT_SECRET}",
        description="JWT signing key (must be loaded from environment variable)",
    )
    algorithm: str = Field(default="HS256", description="JWT signing algorithm")
    access_token_expire_minutes: int = Field(
        default=15, description="Access token expiration time in minutes", gt=0
    )
    refresh_token_expire_days: int = Field(
        default=7, description="Refresh token expiration time in days", gt=0
    )

    # Password Policy
    password_min_length: int = Field(default=8, description="Minimum password length", ge=4, le=128)
    password_require_uppercase: bool = Field(
        default=True, description="Require at least one uppercase letter"
    )
    password_require_lowercase: bool = Field(
        default=True, description="Require at least one lowercase letter"
    )
    password_require_number: bool = Field(default=True, description="Require at least one number")
    password_require_special: bool = Field(
        default=False, description="Require at least one special character"
    )

    # Feature Flags
    require_email_verification: bool = Field(
        default=False, description="Require users to verify email before login"
    )
    allow_password_reset: bool = Field(default=True, description="Enable password reset flow")
    allow_signup: bool = Field(default=True, description="Allow public user registration")
    session_strategy: Literal["jwt", "database"] = Field(
        default="jwt",
        description="Session management strategy (jwt=stateless, database=server-side)",
    )

    # Rate Limiting (future implementation)
    login_rate_limit: str = Field(
        default="5/15minutes",
        description="Rate limit for login attempts (format: count/period)",
    )
    signup_rate_limit: str = Field(
        default="3/hour",
        description="Rate limit for signup attempts (format: count/period)",
    )

    # RBAC (Role-Based Access Control)
    roles: list[Role] = Field(
        default_factory=list,
        description="Role definitions for RBAC. Empty list means role-free auth.",
    )
    default_role: str = Field(default="user", description="Default role assigned to new users")

    # User Model Configuration
    user_model: str = Field(
        default="User",
        description="Name of the user model in specs (must exist if auth enabled)",
    )
    username_field: str = Field(
        default="email",
        description="Field to use for username (typically 'email' or 'username')",
    )

    # Email Configuration (optional, for password reset and verification)
    email_backend: Literal["console", "smtp", "none"] = Field(
        default="console",
        description="Email backend: console (dev), smtp (production), none (disabled)",
    )
    smtp_host: str | None = Field(
        default=None, description="SMTP server hostname (e.g., smtp.gmail.com)"
    )
    smtp_port: int = Field(default=587, description="SMTP server port", gt=0, le=65535)
    smtp_username: str | None = Field(default=None, description="SMTP authentication username")
    smtp_password: str | None = Field(default=None, description="SMTP authentication password")
    from_email: str = Field(
        default="noreply@example.com",
        description="From email address for authentication emails",
    )

    @field_validator("secret_key")
    @classmethod
    def validate_secret_key(cls, v: str, info) -> str:
        """Validate secret key is set properly.

        If auth is enabled, we expect the secret key to be loaded from
        an environment variable at runtime. The placeholder "${JWT_SECRET}"
        is acceptable during spec definition.
        """
        # Allow placeholder during spec definition
        # Actual validation happens at generation/runtime
        if v and not v.strip():
            raise ValueError("secret_key cannot be empty string")
        return v

    @field_validator("username_field")
    @classmethod
    def validate_username_field(cls, v: str) -> str:
        """Validate username field name."""
        if v not in ["email", "username"]:
            raise ValueError(f"username_field must be 'email' or 'username', got '{v}'")
        return v

    @field_validator("default_role")
    @classmethod
    def validate_default_role_exists(cls, v: str, info) -> str:
        """Validate default role exists in roles list (if roles are defined)."""
        # This validation is lenient - if roles list is empty, any default is ok
        # More strict validation happens in validators.py with full context
        return v

    @model_validator(mode="after")
    def validate_preset_requirements(self) -> AuthConfig:
        """Validate that required fields are set for the chosen preset."""
        if not self.enabled:
            return self

        if self.preset == "api_key":
            # API key auth doesn't need JWT settings, user model, etc.
            # Just needs the api_key config which has defaults
            pass
        elif self.preset == "jwt" and (not self.secret_key or self.secret_key == ""):
            # JWT requires secret key to be configured
            raise ValueError("secret_key is required for JWT authentication")

        return self

    model_config = {"extra": "forbid"}
