"""
schemas.py
----------
Pydantic contracts for every stage boundary in the transcription → summarization → SEO pipeline.
These are also the queue message envelopes when stages run over RabbitMQ.

Rule: no stage accepts or emits a plain dict. It accepts an input schema, emits an output schema.
"""
from __future__ import annotations

from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field, model_validator


# ── Enums ─────────────────────────────────────────────────────────────────────

class SummarizationTask(str, Enum):
    ABSTRACT    = "abstract"
    TITLE       = "title"
    CAPTION     = "caption"
    DESCRIPTION = "description"


# ── Stage 1: Transcription ────────────────────────────────────────────────────

class TranscriptionInput(BaseModel):
    """Entry point to the pipeline. Exactly one source is required."""
    audio_path: Optional[str] = None
    video_path: Optional[str] = None
    video_url:  Optional[str] = None
    text:       Optional[str] = None        # skip transcription entirely if provided
    video_id:   Optional[str] = None        # inferred from audio/video path if absent
    language:   str = "english"
    model_size: str = "base"                # whisper model size

    @model_validator(mode="after")
    def requires_at_least_one_source(self) -> "TranscriptionInput":
        if not any([self.audio_path, self.video_path, self.video_url, self.text]):
            raise ValueError(
                "TranscriptionInput requires at least one of: "
                "audio_path, video_path, video_url, text"
            )
        return self


class WhisperSegment(BaseModel):
    start: float
    end:   float
    text:  str


class TranscriptionResult(BaseModel):
    """Output of Stage 1. Also the queue message emitted to the summarization consumer."""
    video_id:  str
    text:      str
    segments:  List[WhisperSegment] = Field(default_factory=list)
    language:  str = "english"
    source:    str  # "whisper" | "passthrough"


# ── Stage 2: Summarization ────────────────────────────────────────────────────

class SummarizationRequest(BaseModel):
    """Input to Stage 2. Wraps the transcription result with generation parameters."""
    transcription:  TranscriptionResult
    task:           SummarizationTask = SummarizationTask.ABSTRACT
    min_length:     int = 100
    max_length:     int = 512
    keyword_count:  int = 10


class SummarizationResult(BaseModel):
    """Output of Stage 2. Also the queue message emitted to the SEO consumer."""
    video_id:        str
    raw_summary:     str            # LED draft
    refined_summary: str            # FLAN-refined output
    keywords:        List[str] = Field(default_factory=list)
    task:            SummarizationTask


# ── Stage 3: SEO ──────────────────────────────────────────────────────────────

class MediaMeta(BaseModel):
    """
    Caller-supplied media metadata. Must be wired explicitly; nothing is inferred
    from environment or filesystem without the caller's knowledge.
    """
    domain:           str                                # e.g. "https://typicallyoutliers.com"
    video_path:       Optional[str] = None
    audio_path:       Optional[str] = None
    thumbnail_paths:  List[str] = Field(default_factory=list)
    thumbnails_dir:   Optional[str] = None
    filename:         Optional[str] = None
    duration_seconds: Optional[float] = None
    duration_formatted: Optional[str] = None


class ThumbnailInfo(BaseModel):
    file_path: Optional[str] = None
    alt_text:  str = ""


class SEOPayload(BaseModel):
    """Output of Stage 3. Final publishable artifact."""
    video_id:          str
    seo_title:         str
    seo_description:   str
    seo_tags:          List[str]
    keywords_str:      str
    thumbnail:         ThumbnailInfo
    duration_seconds:  Optional[float] = None
    duration_formatted: Optional[str] = None
    schema_markup:     Dict[str, Any]   # kept as dict for direct JSON-LD serialization
    social_metadata:   Dict[str, str]
    category:          str = "General"
    uploader:          Dict[str, str] = Field(default_factory=dict)
    publication_date:  str
    canonical_url:     str


# ── Pipeline envelope ─────────────────────────────────────────────────────────

class PipelineResult(BaseModel):
    """Full result envelope returned by run_pipeline()."""
    video_id:       str
    transcription:  TranscriptionResult
    summarization:  SummarizationResult
    seo:            SEOPayload


# ── Queue message wrappers ────────────────────────────────────────────────────
# These are the envelope schemas published to / consumed from RabbitMQ.
# Each stage consumes its InboundMessage and publishes its OutboundMessage.

class TranscriptionJobMessage(BaseModel):
    """Published to: transcription_jobs queue."""
    job_id:      str
    inp:         TranscriptionInput
    meta:        MediaMeta
    task:        SummarizationTask = SummarizationTask.ABSTRACT
    keyword_count: int = 10
    title:       Optional[str] = None


class TranscriptionDoneMessage(BaseModel):
    """Published to: summarization_jobs queue after Stage 1 completes."""
    job_id:        str
    transcription: TranscriptionResult
    meta:          MediaMeta
    task:          SummarizationTask
    keyword_count: int
    title:         Optional[str] = None


class SummarizationDoneMessage(BaseModel):
    """Published to: seo_jobs queue after Stage 2 completes."""
    job_id:       str
    summarization: SummarizationResult
    transcription: TranscriptionResult
    meta:          MediaMeta
    title:         Optional[str] = None


class PipelineDoneMessage(BaseModel):
    """Published to: pipeline_results queue after Stage 3 completes."""
    job_id:  str
    result:  PipelineResult
