"""Pydantic schemas for User management endpoints."""

# Import re
import re

# Import uuid
import uuid

# Import datetime from datetime
from datetime import datetime

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator


# ── Username policy ─────────────────────────────────────────────────

_USERNAME_RE = re.compile(r"^[a-zA-Z0-9_-]{3,50}$")
# Assign _RESERVED_USERNAMES = frozenset({
_RESERVED_USERNAMES = frozenset({
    # Literal argument value
    "admin", "root", "system", "api", "null", "undefined",
    # Literal argument value
    "administrator", "superuser", "aegis",
})


# Define function _validate_username
def _validate_username(username: str) -> str:
    """Validate username format and reject reserved names.

    Args:
        username (str): The username string to validate.

    Returns:
        str: The validated username, unchanged.
    """
    # Check: not _USERNAME_RE.match(username)
    if not _USERNAME_RE.match(username):
        # Raise ValueError
        raise ValueError(
            # Literal argument value
            "Username must be 3-50 characters, containing only "
            # Literal argument value
            "letters, digits, underscores, and hyphens"
        )
    # Check: username.lower() in _RESERVED_USERNAMES
    if username.lower() in _RESERVED_USERNAMES:
        # Raise ValueError
        raise ValueError(f"Username '{username}' is reserved")
    # Return username
    return username


# ── Password policy ─────────────────────────────────────────────────

_MIN_PASSWORD_LENGTH = 12

# Assign _PASSWORD_RULES = [
_PASSWORD_RULES: list[tuple[str, str]] = [
    (r"[A-Z]", "at least one uppercase letter"),
    (r"[a-z]", "at least one lowercase letter"),
    (r"[0-9]", "at least one digit"),
    (r"[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>/?`~]", "at least one special character"),
]


# Define function _validate_password_strength
def _validate_password_strength(password: str) -> str:
    """Check that *password* satisfies the complexity policy.

    Rules:

    - Minimum 12 characters
    - At least one uppercase letter
    - At least one lowercase letter
    - At least one digit
    - At least one special character

    Args:
        password (str): The plaintext password to validate.

    Returns:
        str: The validated password, unchanged.
    """
    # Assign errors = []
    errors: list[str] = []

    # Check: len(password) < _MIN_PASSWORD_LENGTH
    if len(password) < _MIN_PASSWORD_LENGTH:
        # Call errors.append()
        errors.append(f"must be at least {_MIN_PASSWORD_LENGTH} characters long")

    # Iterate over _PASSWORD_RULES
    for pattern, description in _PASSWORD_RULES:
        # Check: not re.search(pattern, password)
        if not re.search(pattern, password):
            # Call errors.append()
            errors.append(description)

    # Check: errors
    if errors:
        # Raise ValueError
        raise ValueError(
            # Literal argument value
            "Password does not meet complexity requirements: " + "; ".join(errors)
        )

    # Return password
    return password


# ── Create ──────────────────────────────────────────────────────────

class UserCreate(BaseModel):
    """Payload for creating a new user."""

    # username: str
    username: str
    # Assign email = None
    email: str | None = None
    # password: str
    password: str
    # Assign role = "viewer"
    role: str = "viewer"

    # Apply the @field_validator decorator
    @field_validator("username")
    # Apply the @classmethod decorator
    @classmethod
    # Define function username_format
    def username_format(cls, v: str) -> str:
        """Validate the username field against the platform policy.

        Args:
            v (str): Raw username value from the request body.

        Returns:
            str: The validated username.
        """
        # Return _validate_username(v)
        return _validate_username(v)

    # Apply the @field_validator decorator
    @field_validator("password")
    # Apply the @classmethod decorator
    @classmethod
    # Define function password_strength
    def password_strength(cls, v: str) -> str:
        """Validate the password field against the complexity policy.

        Args:
            v (str): Raw password value from the request body.

        Returns:
            str: The validated password.
        """
        # Return _validate_password_strength(v)
        return _validate_password_strength(v)


# ── Update ──────────────────────────────────────────────────────────

class UserUpdate(BaseModel):
    """Payload for partially updating an existing user.

    Every field is optional so callers send only what changed.
    """

    # Assign email = None
    email: str | None = None
    # Assign role = None
    role: str | None = None
    # Assign is_active = None
    is_active: bool | None = None
    # Assign password = None
    password: str | None = None

    # Apply the @field_validator decorator
    @field_validator("password")
    # Apply the @classmethod decorator
    @classmethod
    # Define function password_strength
    def password_strength(cls, v: str | None) -> str | None:
        """Validate the password field when provided.

        Args:
            v (str | None): Raw password value, or ``None`` when unchanged.

        Returns:
            str | None: The validated password, or ``None``.
        """
        # Check: v is not None
        if v is not None:
            # Return _validate_password_strength(v)
            return _validate_password_strength(v)
        # Return v
        return v


# ── Read (full) ─────────────────────────────────────────────────────

class PasswordChange(BaseModel):
    """Payload for changing the current user's password."""

    # current_password: str
    current_password: str
    # new_password: str
    new_password: str

    # Apply the @field_validator decorator
    @field_validator("new_password")
    # Apply the @classmethod decorator
    @classmethod
    # Define function new_password_strength
    def new_password_strength(cls, v: str) -> str:
        """Validate the new password against the complexity policy.

        Args:
            v (str): Raw new-password value from the request body.

        Returns:
            str: The validated new password.
        """
        # Return _validate_password_strength(v)
        return _validate_password_strength(v)


class UserPreferencesUpdate(BaseModel):
    """Payload for updating current user's notification preferences and Jira/Tempo settings."""

    notification_preferences: dict | None = None
    jira_account_id: str | None = None
    # Personal Jira API token (Atlassian token) — write-only.
    # Set to empty string "" to clear the token.
    jira_api_token: str | None = None
    # Atlassian email for Jira auth — overrides account email.
    # Set to empty string "" to clear (falls back to account email).
    jira_email: str | None = None
    # Personal Tempo API token — write-only.
    # Set to empty string "" to clear the token.
    tempo_api_token: str | None = None


class UserOut(BaseModel):
    """Complete representation returned by the API."""

    # id: uuid.UUID
    id: uuid.UUID
    # username: str
    username: str
    # Assign email = None
    email: str | None = None
    # role: str
    role: str
    # is_active: bool
    is_active: bool
    # Assign must_change_password = True
    must_change_password: bool = True
    # Assign created_at = None
    created_at: datetime | None = None
    # Assign last_login = None
    last_login: datetime | None = None
    notification_preferences: dict | None = None
    jira_account_id: str | None = None
    jira_email: str | None = None
    # Read from ORM but NEVER exposed in responses — used only to derive *_token_set flags.
    jira_api_token: str | None = Field(default=None, exclude=True)
    tempo_api_token: str | None = Field(default=None, exclude=True)
    # True when the user has the respective token stored.
    jira_token_set: bool = False
    tempo_token_set: bool = False

    # Assign model_config = ConfigDict(from_attributes=True)
    model_config = ConfigDict(from_attributes=True)

    @model_validator(mode="after")
    def _derive_token_set_flags(self) -> "UserOut":
        """Derive *_token_set booleans from the (excluded) raw token fields.

        Uses @model_validator(mode='after') so Pydantic's Rust core calls it
        during FastAPI response serialisation — model_validate() overrides are
        bypassed by FastAPI's __pydantic_validator__.validate_python() path.
        """
        self.jira_token_set = bool(self.jira_api_token)
        self.tempo_token_set = bool(self.tempo_api_token)
        return self
