"""Pydantic schemas for request/response validation."""

from datetime import datetime
from typing import Literal, Optional, Any
from pydantic import BaseModel, ConfigDict


# ---------------------------------------------------------------------------
# Auth / user schemas
# ---------------------------------------------------------------------------
class UserOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    username: str
    role: str
    is_active: int


class LoginResponse(BaseModel):
    token: str
    token_type: str = "bearer"
    username: str
    user: UserOut


class AdminUserCreate(BaseModel):
    username: str
    password: str
    role: str = "annotator"
    is_active: bool = True


class AdminUserUpdate(BaseModel):
    username: Optional[str] = None
    password: Optional[str] = None
    role: Optional[str] = None
    is_active: Optional[bool] = None


class AuditLogOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    actor_user_id: Optional[int] = None
    action: str
    target_type: Optional[str] = None
    target_id: Optional[str] = None
    detail: Optional[dict[str, Any]] = None
    created_at: datetime


class DemoFactoryResetRequest(BaseModel):
    confirmation: str


# ---------------------------------------------------------------------------
# Project schemas
# ---------------------------------------------------------------------------
class ProjectBase(BaseModel):
    name: str
    description: Optional[str] = None
    video_path: Optional[str] = None
    thumbnail_url: Optional[str] = None
    status: Optional[str] = "pending"
    source_type: Optional[str] = "video"
    original_fps: Optional[float] = None
    parse_fps: Optional[float] = 30.0


class ProjectCreate(ProjectBase):
    pass


class ProjectUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    video_path: Optional[str] = None
    thumbnail_url: Optional[str] = None
    status: Optional[str] = None
    source_type: Optional[str] = None
    original_fps: Optional[float] = None
    parse_fps: Optional[float] = None


class ProjectCopyRequest(BaseModel):
    mode: Literal["reset", "full"] = "reset"
    name: Optional[str] = None


class ProjectOut(ProjectBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    owner_user_id: Optional[int] = None
    created_at: datetime
    updated_at: datetime
    frame_count: int = 0


class DemoFactoryResetOut(BaseModel):
    admin_user: UserOut
    project: ProjectOut
    projects: list[ProjectOut]
    deleted_counts: dict[str, int]
    message: str


# ---------------------------------------------------------------------------
# Frame schemas
# ---------------------------------------------------------------------------
class FrameBase(BaseModel):
    frame_index: int
    image_url: str
    width: Optional[int] = None
    height: Optional[int] = None
    timestamp_ms: Optional[float] = None
    source_frame_number: Optional[int] = None


class FrameCreate(FrameBase):
    project_id: int


class FrameOut(FrameBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    project_id: int
    created_at: datetime


# ---------------------------------------------------------------------------
# Template schemas
# ---------------------------------------------------------------------------
class TemplateBase(BaseModel):
    name: str
    description: Optional[str] = None
    color: str
    z_index: int = 0
    mapping_rules: Optional[dict[str, Any]] = None
    classes: Optional[list[dict[str, Any]]] = None
    rules: Optional[list[dict[str, Any]]] = None


class TemplateCreate(TemplateBase):
    pass


class TemplateUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    color: Optional[str] = None
    z_index: Optional[int] = None
    mapping_rules: Optional[dict[str, Any]] = None
    classes: Optional[list[dict[str, Any]]] = None
    rules: Optional[list[dict[str, Any]]] = None


class TemplateOut(TemplateBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    owner_user_id: Optional[int] = None
    created_at: datetime


# ---------------------------------------------------------------------------
# Annotation schemas
# ---------------------------------------------------------------------------
class AnnotationBase(BaseModel):
    project_id: int
    frame_id: Optional[int] = None
    template_id: Optional[int] = None
    mask_data: Optional[dict[str, Any]] = None
    points: Optional[list[list[float]]] = None
    bbox: Optional[list[float]] = None


class AnnotationCreate(AnnotationBase):
    pass


class AnnotationUpdate(BaseModel):
    mask_data: Optional[dict[str, Any]] = None
    points: Optional[list[list[float]]] = None
    bbox: Optional[list[float]] = None
    template_id: Optional[int] = None


class AnnotationOut(AnnotationBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    created_at: datetime
    updated_at: datetime


# ---------------------------------------------------------------------------
# Mask schemas
# ---------------------------------------------------------------------------
class MaskBase(BaseModel):
    annotation_id: int
    mask_url: str
    format: str = "png"


class MaskCreate(MaskBase):
    pass


class MaskOut(MaskBase):
    model_config = ConfigDict(from_attributes=True)

    id: int
    created_at: datetime


# ---------------------------------------------------------------------------
# Processing task schemas
# ---------------------------------------------------------------------------
class ProcessingTaskOut(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    task_type: str
    status: str
    progress: int
    message: Optional[str] = None
    project_id: Optional[int] = None
    celery_task_id: Optional[str] = None
    payload: Optional[dict[str, Any]] = None
    result: Optional[dict[str, Any]] = None
    error: Optional[str] = None
    created_at: datetime
    started_at: Optional[datetime] = None
    finished_at: Optional[datetime] = None
    updated_at: datetime


# ---------------------------------------------------------------------------
# AI schemas
# ---------------------------------------------------------------------------
class PredictRequest(BaseModel):
    image_id: int
    prompt_type: str  # point / box / semantic
    prompt_data: Any
    model: Optional[str] = None
    options: Optional[dict[str, Any]] = None


class PredictResponse(BaseModel):
    polygons: list[list[list[float]]]
    scores: Optional[list[float]] = None


class MaskAnalysisRequest(BaseModel):
    frame_id: Optional[int] = None
    mask_data: dict[str, Any]
    points: Optional[list[list[float]]] = None
    bbox: Optional[list[float]] = None
    extract_skeleton: bool = False


class MaskAnalysisResponse(BaseModel):
    confidence: Optional[float] = None
    confidence_source: str
    topology_anchor_count: int
    topology_anchors: list[list[float]]
    area: float
    bbox: Optional[list[float]] = None
    source: Optional[str] = None
    message: str


class SmoothMaskRequest(BaseModel):
    frame_id: Optional[int] = None
    mask_data: dict[str, Any]
    points: Optional[list[list[float]]] = None
    bbox: Optional[list[float]] = None
    strength: float = 0.0
    method: str = "chaikin"


class SmoothMaskResponse(BaseModel):
    polygons: list[list[list[float]]]
    topology_anchor_count: int
    topology_anchors: list[list[float]]
    area: float
    bbox: Optional[list[float]] = None
    smoothing: dict[str, Any]
    message: str


class PropagationSeed(BaseModel):
    polygons: Optional[list[list[list[float]]]] = None
    holes: Optional[list[list[list[list[float]]]]] = None
    bbox: Optional[list[float]] = None
    points: Optional[list[list[float]]] = None
    labels: Optional[list[int]] = None
    label: Optional[str] = None
    color: Optional[str] = None
    class_metadata: Optional[dict[str, Any]] = None
    template_id: Optional[int] = None
    source_mask_id: Optional[str] = None
    source_annotation_id: Optional[int] = None
    propagation_seed_signature: Optional[str] = None
    smoothing: Optional[dict[str, Any]] = None


class PropagateRequest(BaseModel):
    project_id: int
    frame_id: int
    model: Optional[str] = "sam2.1_hiera_tiny"
    seed: PropagationSeed
    direction: str = "forward"
    max_frames: int = 30
    include_source: bool = False
    save_annotations: bool = True


class PropagateResponse(BaseModel):
    model: str
    direction: str
    source_frame_id: int
    processed_frame_count: int
    created_annotation_count: int
    annotations: list[AnnotationOut]


class PropagateTaskStep(BaseModel):
    seed: PropagationSeed
    direction: str = "forward"
    max_frames: int = 30


class PropagateTaskRequest(BaseModel):
    project_id: int
    frame_id: int
    model: Optional[str] = "sam2.1_hiera_tiny"
    steps: list[PropagateTaskStep]
    include_source: bool = False
    save_annotations: bool = True


class AiModelStatus(BaseModel):
    id: str
    label: str
    available: bool
    loaded: bool = False
    device: str
    supports: list[str]
    message: str
    package_available: bool = False
    checkpoint_exists: bool = False
    checkpoint_path: Optional[str] = None
    python_ok: bool = True
    torch_ok: bool = True
    cuda_required: bool = False
    external_available: bool = False
    external_python: Optional[str] = None


class GpuStatus(BaseModel):
    available: bool
    device: str
    name: Optional[str] = None
    torch_available: bool
    torch_version: Optional[str] = None
    cuda_version: Optional[str] = None


class AiRuntimeStatus(BaseModel):
    selected_model: str
    gpu: GpuStatus
    models: list[AiModelStatus]


# ---------------------------------------------------------------------------
# Export schemas
# ---------------------------------------------------------------------------
class ExportStatus(BaseModel):
    url: str
    format: str
