"""
Pydantic schemas for the user_profile module.
"""
from __future__ import annotations

import re
from datetime import datetime
from typing import List, Optional

from pydantic import BaseModel, EmailStr, Field, model_validator, validator


# ─── Profile ──────────────────────────────────────────────────────────────────

class ProfileUpdateRequest(BaseModel):
    """Fields the user may update on their own profile."""

    first_name: Optional[str] = Field(None, max_length=100)
    last_name: Optional[str] = Field(None, max_length=100)
    middle_name: Optional[str] = Field(None, max_length=100)
    # SECURITY FIX (HIGH): bio had no max_length, allowing unbounded input that could exhaust
    # DB storage or trigger excessive processing. Capped at 1000 characters.
    bio: Optional[str] = Field(None, max_length=1000)
    phone_number: Optional[str] = Field(None, max_length=30)
    address_line_1: Optional[str] = Field(None, max_length=255)
    address_line_2: Optional[str] = Field(None, max_length=255)
    city: Optional[str] = Field(None, max_length=100)
    state: Optional[str] = Field(None, max_length=100)
    zip_code: Optional[str] = Field(None, max_length=20)
    country: Optional[str] = Field(None, max_length=100)

    @validator("phone_number")
    @classmethod
    def validate_phone(cls, v: Optional[str]) -> Optional[str]:
        if v is None:
            return v
        pattern = r"^\+?[1-9]\d{1,14}$"
        if not re.match(pattern, v):
            raise ValueError("phone_number must be in E.164 format (e.g. +14155552671)")
        return v


class ProfileResponse(BaseModel):
    """Full profile response serialized from the User ORM object."""

    id: int
    email: str
    username: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    middle_name: Optional[str] = None
    full_name: Optional[str] = None
    bio: Optional[str] = None
    phone_number: Optional[str] = None
    address_line_1: Optional[str] = None
    address_line_2: Optional[str] = None
    city: Optional[str] = None
    state: Optional[str] = None
    zip_code: Optional[str] = None
    country: Optional[str] = None
    avatar_url: Optional[str] = None
    twofa_enabled: bool = False
    last_password_changed_at: Optional[datetime] = None
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

    model_config = {"from_attributes": True}


class AvatarUrlResponse(BaseModel):
    avatar_url: str
    expires_in: int = 3600


# ─── Password ─────────────────────────────────────────────────────────────────

class ChangePasswordRequest(BaseModel):
    current_password: str
    new_password: str = Field(..., min_length=8)
    confirm_new_password: str

    @model_validator(mode="after")
    def passwords_match(self) -> "ChangePasswordRequest":
        if self.new_password != self.confirm_new_password:
            raise ValueError("new_password and confirm_new_password do not match")
        return self


class ChangePasswordResponse(BaseModel):
    message: str
    sessions_revoked: int = 0


# ─── 2FA ──────────────────────────────────────────────────────────────────────

class TwoFAUpdateRequest(BaseModel):
    twofa_enabled: bool


class TwoFAUpdateResponse(BaseModel):
    twofa_enabled: bool
    message: str


# ─── Notification Preferences ─────────────────────────────────────────────────

class NotificationPrefsRequest(BaseModel):
    notify_on_new_login: Optional[bool] = None
    notify_on_password_change: Optional[bool] = None
    notify_on_new_device: Optional[bool] = None


class NotificationPrefsResponse(BaseModel):
    notify_on_new_login: bool
    notify_on_password_change: bool
    notify_on_new_device: bool
    updated_at: Optional[datetime] = None

    model_config = {"from_attributes": True}


# ─── Sessions ─────────────────────────────────────────────────────────────────

class SessionGeoInfo(BaseModel):
    country: Optional[str] = None
    region: Optional[str] = None
    city: Optional[str] = None
    latitude: Optional[float] = None
    longitude: Optional[float] = None


class SessionDeviceInfo(BaseModel):
    browser: Optional[str] = None
    browser_version: Optional[str] = None
    os: Optional[str] = None
    os_version: Optional[str] = None
    device_type: Optional[str] = None
    brand: Optional[str] = None


class SessionResponse(BaseModel):
    id: int
    ip_address: Optional[str] = None
    geo: SessionGeoInfo
    device: SessionDeviceInfo
    is_active: bool
    is_revoked: bool
    is_current: bool
    created_at: Optional[datetime] = None
    last_activity_at: Optional[datetime] = None


class SessionListResponse(BaseModel):
    items: List[SessionResponse]
    total: int
    page: int
    per_page: int
    pages: int


# ─── Email Change ─────────────────────────────────────────────────────────────

class EmailChangeInitiateRequest(BaseModel):
    new_email: EmailStr


class EmailChangeInitiateResponse(BaseModel):
    message: str


# ─── Password Policy ──────────────────────────────────────────────────────────

class PasswordPolicyResponse(BaseModel):
    id: int
    merchant_id: Optional[int] = None
    min_length: int
    require_uppercase: bool
    require_lowercase: bool
    require_digit: bool
    require_special: bool
    max_age_days: Optional[int] = None
    grace_period_days: int
    history_count: int
    is_active: bool
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

    model_config = {"from_attributes": True}


class PasswordPolicyCreateRequest(BaseModel):
    merchant_id: Optional[int] = Field(None, description="NULL for global policy")
    # SECURITY (MEDIUM): min_length allows values as low as 4. PCI DSS Requirement 8.3.6
    # mandates a minimum password length of at least 12 characters for new implementations
    # (8 characters with MFA). Allowing a policy with min_length=4 violates PCI DSS.
    # Raise the lower bound to ge=8 at minimum; ge=12 is recommended for PCI compliance.
    min_length: int = Field(8, ge=4, le=128)
    require_uppercase: bool = True
    require_lowercase: bool = True
    require_digit: bool = True
    require_special: bool = False
    max_age_days: Optional[int] = Field(None, ge=1, le=3650)
    grace_period_days: int = Field(7, ge=0, le=30)
    history_count: int = Field(5, ge=0, le=24)
    is_active: bool = True


class PasswordPolicyUpdateRequest(BaseModel):
    min_length: Optional[int] = Field(None, ge=4, le=128)
    require_uppercase: Optional[bool] = None
    require_lowercase: Optional[bool] = None
    require_digit: Optional[bool] = None
    require_special: Optional[bool] = None
    max_age_days: Optional[int] = Field(None, ge=1, le=3650)
    grace_period_days: Optional[int] = Field(None, ge=0, le=30)
    history_count: Optional[int] = Field(None, ge=0, le=24)
    is_active: Optional[bool] = None
