"""Pydantic domain models.

These are the typed shapes passed between pipeline stages and the API layer.
They are deliberately independent of any DB row or external API response — the
providers and repositories convert to/from these.
"""

from __future__ import annotations

from datetime import date, datetime
from typing import Any, Literal, Optional

from pydantic import BaseModel, ConfigDict, Field


# ── External Hub (cleaned Planner mirror) shapes ──────────────────────────

class Show(BaseModel):
    """A show exactly DAYS_TO_SHOW days out (the orchestration target)."""

    model_config = ConfigDict(extra="allow")

    show_id: str
    show_date: date
    city: str
    country: str
    venue: Optional[str] = None
    capacity: Optional[int] = None
    ticket_url: Optional[str] = None


class PastShow(BaseModel):
    """The most recent past Max Amini show in the same city (ignore venue)."""

    model_config = ConfigDict(extra="allow")

    show_id: str
    show_date: date
    city: str
    country: str
    venue: Optional[str] = None
    capacity: Optional[int] = None
    tickets_sold: Optional[int] = None
    sold_pct: Optional[float] = None


# ── Voice ─────────────────────────────────────────────────────────────────

class VoiceProfile(BaseModel):
    """Loaded from voice_profiles/<comedian>.json — comedian IP guidance.

    Holds tone, persona, signature bits, topics to lean into and sensitivities
    to avoid. AI stages consume this in their prompts.
    """

    comedian_id: str
    display_name: str
    persona: str
    tone: list[str] = Field(default_factory=list)
    signature_bits: list[str] = Field(default_factory=list)
    topics_to_lean_into: list[str] = Field(default_factory=list)
    sensitivities_to_avoid: list[str] = Field(default_factory=list)
    vocabulary: dict[str, list[str]] = Field(default_factory=dict)
    extra: dict[str, Any] = Field(default_factory=dict)


# ── AI provider result shapes ─────────────────────────────────────────────

class CityIntelligence(BaseModel):
    city: str
    points_of_pride: list[str] = Field(default_factory=list)
    sensitivities_to_avoid: list[str] = Field(default_factory=list)
    local_conversation_today: Optional[str] = None
    audience_competitors: list[str] = Field(default_factory=list)
    raw_provider_payload: dict[str, Any] = Field(default_factory=dict)


class NewsItem(BaseModel):
    title: str
    summary: str
    date: Optional[str] = None
    url: Optional[str] = None
    is_competitor: bool = False


class PerplexityNewsResult(BaseModel):
    recent_news: list[NewsItem] = Field(default_factory=list)
    competitor_warnings: list[NewsItem] = Field(default_factory=list)


class VerifiedFact(BaseModel):
    claim: str
    confidence: Literal["high", "medium", "low"] = "medium"
    source: Optional[str] = None


class RemovedFact(BaseModel):
    claim: str
    removed_reason: str


class FactCheckResult(BaseModel):
    verified_facts: list[VerifiedFact] = Field(default_factory=list)
    removed_facts: list[RemovedFact] = Field(default_factory=list)


class GeneratedIdea(BaseModel):
    """One of the 20 GPT-4o crowd-work ideas."""

    title: str
    angle: str
    city_specificity: str
    risk_note: Optional[str] = None


class RankedIdea(GeneratedIdea):
    """A GPT-4o idea after Claude's rank / filter pass (top 10)."""

    rank: int
    reasoning: Optional[str] = None
    safety_notes: Optional[str] = None


# ── Brief lifecycle ──────────────────────────────────────────────────────

BriefStatus = Literal[
    "pending", "generating", "generated", "deploying", "published", "failed"
]

RunStatus = Literal["running", "ok", "failed", "partial"]

StageState = Literal["pending", "running", "ok", "failed", "skipped"]

# Stage column names on city_briefs_runs, in execution order.
STAGE_COLUMNS: tuple[str, ...] = (
    "hub_lookup",
    "previous_show_lookup",
    "historical_sales_lookup",
    "voice_profile_load",
    "youtube_transcripts",
    "perplexity_city",
    "perplexity_news",
    "openai_fact_check",
    "openai_generation",
    "claude_ranking",
    "html_generation",
    "vps_deployment",
    "url_verification",
    "og_verification",
)


class BriefRecord(BaseModel):
    """Snapshot row from `city_briefs`."""

    model_config = ConfigDict(extra="allow")

    brief_id: str
    show_id: str
    show_date: date
    city: str
    country: str
    venue: Optional[str] = None
    capacity: Optional[int] = None
    comedian_id: str = "max-amini"
    generated_at: Optional[datetime] = None
    published_at: Optional[datetime] = None
    previous_show_date: Optional[date] = None
    previous_show_venue: Optional[str] = None
    previous_tickets_sold: Optional[int] = None
    city_intelligence: Optional[dict[str, Any]] = None
    recent_news: Optional[list[Any]] = None
    verified_facts: Optional[list[Any]] = None
    removed_facts: Optional[list[Any]] = None
    generated_topics: Optional[list[Any]] = None
    ranked_topics: Optional[list[Any]] = None
    crowd_work: Optional[list[Any]] = None
    competitor_warnings: Optional[list[Any]] = None
    public_url: Optional[str] = None
    status: BriefStatus = "pending"
    generation_duration: Optional[int] = None
    error: Optional[str] = None


class RunRecord(BaseModel):
    """Snapshot row from `city_briefs_runs` — observability per stage."""

    model_config = ConfigDict(extra="allow")

    brief_run_id: str
    show_id: Optional[str] = None
    show_date: Optional[date] = None

    hub_lookup: StageState = "pending"
    previous_show_lookup: StageState = "pending"
    historical_sales_lookup: StageState = "pending"
    voice_profile_load: StageState = "pending"
    youtube_transcripts: StageState = "pending"
    perplexity_city: StageState = "pending"
    perplexity_news: StageState = "pending"
    openai_fact_check: StageState = "pending"
    openai_generation: StageState = "pending"
    claude_ranking: StageState = "pending"
    html_generation: StageState = "pending"
    vps_deployment: StageState = "pending"
    vps_deployment_public_url: Optional[str] = None
    url_verification: StageState = "pending"
    og_verification: StageState = "pending"

    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    status: RunStatus = "running"
    duration: Optional[int] = None
    error: Optional[str] = None
    retry_count: int = 0