"""Scheduling Phase 2 (INDL-34) — Pydantic schemas for the /scheduling router.

Design notes:
  * API field names ``date`` / ``start_time`` map to ORM ``scheduled_date`` /
    ``scheduled_time``. The service layer does the mapping explicitly; output
    dicts are built by hand (never blind model_validate) for those names.
  * Create/Update use ``extra='forbid'`` to block mass assignment. They NEVER
    declare tenant_id / created_by / id / deleted_at / reminder_job_id.
    ``ServiceUpdate`` additionally omits ``status`` (PATCH /status only).
  * ``reminder_job_id`` never appears in any OUTPUT schema.
  * ``ActivityLogEntrySchema`` allow-lists output fields and NEVER exposes the
    raw ``metadata`` JSONB (API8 / S-09).
"""
import datetime as dt
from enum import Enum
from typing import List, Optional
from uuid import UUID

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

from src.core.validators import validate_phone

# Text field caps (A03 / S-12 — bound JSONB/DB growth and injection surface).
_MAX_NAME = 255
_MAX_EMAIL = 255
_MAX_SHORT = 255
_MAX_NOTE = 5000
_MAX_DETAILS = 2000
_MAX_TIME_LABEL = 20

# Legal creation statuses — `confirmed` is only reachable via PATCH /status.
CREATE_STATUSES = {"draft", "awaiting_family_confirm"}


class SchedulingView(str, Enum):
    """List view mode. Freeform values are rejected with 422."""

    LIST = "list"
    KANBAN = "kanban"
    WEEK = "week"


class RunSheetItemSchema(BaseModel):
    """A single run-sheet line (input + output)."""

    model_config = ConfigDict(extra="forbid", from_attributes=True)

    sort_order: int = 0
    time_label: Optional[str] = Field(default=None, max_length=_MAX_TIME_LABEL)
    details: Optional[str] = Field(default=None, max_length=_MAX_DETAILS)


class ServiceCreate(BaseModel):
    """Create payload. ``status`` restricted to {draft, awaiting_family_confirm}."""

    model_config = ConfigDict(extra="forbid")

    service_type: str = Field(max_length=100)
    decedent_name: Optional[str] = Field(default=None, max_length=_MAX_NAME)
    record_id: Optional[UUID] = None
    # Optional so a draft can be saved with only service_type + decedent
    # (INDL-35 AC-17); service_date/service_time columns are nullable. A
    # scheduled service supplies them via the form's Schedule validation.
    date: Optional[dt.date] = None
    start_time: Optional[dt.time] = None
    duration_minutes: Optional[int] = Field(default=60, ge=0, le=1440)
    plot_location: Optional[str] = Field(default=None, max_length=_MAX_SHORT)
    section: Optional[str] = Field(default=None, max_length=_MAX_SHORT)
    officiant: Optional[str] = Field(default=None, max_length=_MAX_NAME)
    family_contact_name: Optional[str] = Field(default=None, max_length=_MAX_NAME)
    family_contact_phone: Optional[str] = Field(default=None, max_length=50)
    family_contact_email: Optional[EmailStr] = Field(default=None, max_length=_MAX_EMAIL)
    expected_attendees: Optional[int] = Field(default=None, ge=0)
    event_note: Optional[str] = Field(default=None, max_length=_MAX_NOTE)
    crew_member_ids: List[UUID] = Field(default_factory=list)
    run_sheet_items: List[RunSheetItemSchema] = Field(default_factory=list)
    equipment_needed: Optional[str] = Field(default=None, max_length=_MAX_NOTE)
    notes_for_crew: Optional[str] = Field(default=None, max_length=_MAX_NOTE)
    notify_family_confirmation: bool = False
    notify_family_reminder_24h: bool = False
    status: str = "draft"

    @field_validator("family_contact_phone", mode="before")
    @classmethod
    def _validate_phone(cls, v: Optional[str]) -> Optional[str]:
        return validate_phone(v)

    @field_validator("status")
    @classmethod
    def _validate_status(cls, v: str) -> str:
        if v not in CREATE_STATUSES:
            raise ValueError(
                "status on create must be one of: draft, awaiting_family_confirm"
            )
        return v


class ServiceUpdate(BaseModel):
    """Full replace. Deliberately has NO ``status`` field — a body containing
    ``status`` triggers 422 via ``extra='forbid'`` (S-03 / A04)."""

    model_config = ConfigDict(extra="forbid")

    service_type: str = Field(max_length=100)
    decedent_name: Optional[str] = Field(default=None, max_length=_MAX_NAME)
    record_id: Optional[UUID] = None
    # Optional so a draft can be saved with only service_type + decedent
    # (INDL-35 AC-17); service_date/service_time columns are nullable. A
    # scheduled service supplies them via the form's Schedule validation.
    date: Optional[dt.date] = None
    start_time: Optional[dt.time] = None
    duration_minutes: Optional[int] = Field(default=60, ge=0, le=1440)
    plot_location: Optional[str] = Field(default=None, max_length=_MAX_SHORT)
    section: Optional[str] = Field(default=None, max_length=_MAX_SHORT)
    officiant: Optional[str] = Field(default=None, max_length=_MAX_NAME)
    family_contact_name: Optional[str] = Field(default=None, max_length=_MAX_NAME)
    family_contact_phone: Optional[str] = Field(default=None, max_length=50)
    family_contact_email: Optional[EmailStr] = Field(default=None, max_length=_MAX_EMAIL)
    expected_attendees: Optional[int] = Field(default=None, ge=0)
    event_note: Optional[str] = Field(default=None, max_length=_MAX_NOTE)
    crew_member_ids: List[UUID] = Field(default_factory=list)
    run_sheet_items: List[RunSheetItemSchema] = Field(default_factory=list)
    equipment_needed: Optional[str] = Field(default=None, max_length=_MAX_NOTE)
    notes_for_crew: Optional[str] = Field(default=None, max_length=_MAX_NOTE)
    notify_family_confirmation: bool = False
    notify_family_reminder_24h: bool = False

    @field_validator("family_contact_phone", mode="before")
    @classmethod
    def _validate_phone(cls, v: Optional[str]) -> Optional[str]:
        return validate_phone(v)


class StatusUpdateRequest(BaseModel):
    """PATCH /status body. Must be 'confirmed'."""

    model_config = ConfigDict(extra="forbid")

    status: str

    @field_validator("status")
    @classmethod
    def _validate_status(cls, v: str) -> str:
        if v != "confirmed":
            raise ValueError("status must be 'confirmed'")
        return v


class CrewMemberSummary(BaseModel):
    """Crew member as embedded in ServiceDetail."""

    model_config = ConfigDict(from_attributes=True)

    id: UUID
    name: str
    email: Optional[str] = None
    phone: Optional[str] = None
    is_available: Optional[bool] = None


class DocumentSchema(BaseModel):
    """Document with an on-demand presigned download URL (never stored)."""

    id: UUID
    file_name: str
    file_size: int
    mime_type: str
    download_url: Optional[str] = None
    created_at: dt.datetime
    uploaded_by: UUID


class ActivityLogEntrySchema(BaseModel):
    """Output allow-list — the raw ``metadata`` JSONB is NEVER included."""

    id: UUID
    event_type: str
    from_status: Optional[str] = None
    to_status: Optional[str] = None
    description: str
    actor_name: Optional[str] = None
    created_at: dt.datetime


class ServiceListItem(BaseModel):
    """Row in the list/kanban response. Output-only, hand-built by the service."""

    id: UUID
    service_type: str
    decedent_name: Optional[str] = None
    record_id: Optional[UUID] = None
    date: Optional[dt.date] = None
    start_time: Optional[dt.time] = None
    status: str
    family_contact_name: Optional[str] = None
    plot_location: Optional[str] = None
    crew: List[str] = Field(default_factory=list)
    officiant: Optional[str] = None


class ServiceCalendarItem(BaseModel):
    """PII-minimized week/calendar row (INDL-36). Output-only, hand-built by the
    service. Deliberately OMITS family_contact_name and all other PII — only the
    minimum needed to render a weekly calendar grid.

    Uses the same API field names as ServiceListItem for the schedule day/time
    (``date`` / ``start_time``)."""

    id: UUID
    service_type: str
    decedent_name: Optional[str] = None
    plot_location: Optional[str] = None
    section: Optional[str] = None
    date: Optional[dt.date] = None
    start_time: Optional[dt.time] = None
    duration_minutes: Optional[int] = None
    status: str
    crew_count: int = 0


class ServiceDetail(BaseModel):
    """Full detail. Note: NO reminder_job_id, NO tenant_id."""

    id: UUID
    service_type: str
    decedent_name: Optional[str] = None
    record_id: Optional[UUID] = None
    plot_id: Optional[UUID] = None
    memorial_id: Optional[UUID] = None
    date: Optional[dt.date] = None
    start_time: Optional[dt.time] = None
    duration_minutes: Optional[int] = None
    status: str
    plot_location: Optional[str] = None
    section: Optional[str] = None
    officiant: Optional[str] = None
    family_contact_name: Optional[str] = None
    family_contact_phone: Optional[str] = None
    family_contact_email: Optional[str] = None
    expected_attendees: Optional[int] = None
    event_note: Optional[str] = None
    equipment_needed: Optional[str] = None
    notes_for_crew: Optional[str] = None
    notify_family_confirmation: bool = False
    notify_family_reminder_24h: bool = False
    crew: List[CrewMemberSummary] = Field(default_factory=list)
    run_sheet_items: List[RunSheetItemSchema] = Field(default_factory=list)
    documents: List[DocumentSchema] = Field(default_factory=list)
    activity_log: List[ActivityLogEntrySchema] = Field(default_factory=list)
    created_by: Optional[UUID] = None
    created_at: dt.datetime
    updated_at: dt.datetime
