"""Pydantic schemas. The API contract - the single coupling point to the SPA.

The TypeScript client is generated from the OpenAPI schema these produce
(`make api-types`). Never hand-write a TS interface mirroring one of these.
"""

from __future__ import annotations

from datetime import datetime

from pydantic import BaseModel, ConfigDict, Field

from app.models.tables import TicketPriority, TicketStatus, UsageDirection


class Meta(BaseModel):
    name: str
    version: str
    app_env: str


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

    id: int
    equipment_no: str
    description: str
    line: str | None = None
    manufacturer: str | None = None


class AssetMatch(BaseModel):
    """A guess at which asset a spoken description refers to."""

    asset: AssetOut
    score: float
    matched_on: str


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

    id: int
    part_number: str
    part_name: str
    uom: str | None = None
    location: str | None = None
    area: str | None = None
    part_type: str | None = None
    barcode: str | None = None
    unit_cost: float | None = None
    quantity: int


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

    id: int
    name: str
    role: str


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

    id: int
    mime: str
    size_bytes: int
    uploaded_at: datetime


class EventOut(BaseModel):
    id: int
    kind: str
    detail: str | None = None
    actor_name: str | None = None
    at: datetime


class PartUsageOut(BaseModel):
    id: int
    part_id: int
    part_number: str
    part_name: str
    quantity: float
    direction: UsageDirection
    actor_name: str | None = None
    at: datetime


class TicketSummary(BaseModel):
    id: int
    number: int
    display_number: str
    description: str
    status: TicketStatus
    priority: TicketPriority
    asset_equipment_no: str | None = None
    asset_description: str | None = None
    reported_by_name: str | None = None
    assigned_to_name: str | None = None
    reported_at: datetime
    scheduled_for: datetime | None = None
    media_count: int


class TicketDetail(TicketSummary):
    parent_ticket_id: int | None = None
    parent_display_number: str | None = None
    closed_at: datetime | None = None
    # Always populated by services.tickets.to_detail. Deliberately required
    # rather than defaulted: a default makes them optional in the OpenAPI
    # schema, which makes the generated TS type `T[] | undefined` and forces
    # null-checks on the client for a case that cannot happen.
    media: list[MediaOut]
    events: list[EventOut]
    parts_used: list[PartUsageOut]


class TicketCreate(BaseModel):
    description: str = Field(min_length=1, max_length=8000)
    asset_id: int | None = None
    priority: TicketPriority = TicketPriority.normal
    parent_ticket_id: int | None = None


class TicketUpdate(BaseModel):
    status: TicketStatus | None = None
    priority: TicketPriority | None = None
    assigned_to_id: int | None = None
    scheduled_for: datetime | None = None


class PartUsageCreate(BaseModel):
    part_id: int
    quantity: float = Field(gt=0)
    direction: UsageDirection = UsageDirection.used


class NoteCreate(BaseModel):
    detail: str = Field(min_length=1, max_length=4000)


class ErrorOut(BaseModel):
    """Structured errors the frontend can act on (react.md)."""

    error_code: str
    summary: str
    details: str | None = None
