"""
Pydantic data models for the AI Video Production Pipeline.
All data flows between agents use these typed models.
"""

from __future__ import annotations

from datetime import datetime, timezone
from enum import Enum
from typing import Any, Optional

from pydantic import BaseModel, Field


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class TargetPlatform(str, Enum):
    TIKTOK = "tiktok"
    INSTAGRAM_REELS = "instagram_reels"
    YOUTUBE_SHORTS = "youtube_shorts"
    YOUTUBE = "youtube"


class PipelineMode(str, Enum):
    PLAN = "plan"                    # Only generate the production plan (prompts, timeline)
    PLAN_AND_IMAGES = "plan_images"  # Generate plan + images (no video/TTS)
    GENERATE = "generate"            # Full generation: plan + images + video + TTS


# ---------------------------------------------------------------------------
# Style Presets (Decoupled from Character)
# ---------------------------------------------------------------------------

class WorldStyle(BaseModel):
    """Style preset for the world/environment rendering."""
    
    id: Optional[str] = Field(default=None, description="Unique identifier")
    name: str = Field(description="Name of the world style preset")
    preset_type: str = Field(
        default="custom",
        description="Type: 'realistic', 'diorama', or 'custom'"
    )
    background_rules: str = Field(
        default="Infer the environment directly from the script. Place the character naturally inside real-world locations.",
        description="Rules for background/environment rendering"
    )
    floor_rules: str = Field(
        default="Natural floor matching environment (concrete, grass, wood, etc.)",
        description="Rules for floor/ground rendering"
    )
    secondary_characters_rules: str = Field(
        default="Photorealistic human skin with natural texture, pores, and imperfections. Normal human proportions.",
        description="Rules for secondary characters (skin, style)"
    )
    lighting_rules: str = Field(
        default="Real-world lighting matching the environment, natural shadows, photorealistic cinematic realism.",
        description="Lighting style rules"
    )
    props_rules: str = Field(
        default="Environment-appropriate props and objects. Full scene composition allowed.",
        description="Rules for props and objects in scene"
    )
    architecture_allowed: bool = Field(
        default=True,
        description="Whether architectural elements (walls, ceilings, etc.) are allowed"
    )


class ClothingStyle(BaseModel):
    """Style preset for character clothing."""
    
    id: Optional[str] = Field(default=None, description="Unique identifier")
    name: str = Field(description="Name of the clothing style preset")
    preset_type: str = Field(
        default="custom",
        description="Type: 'minimal', 'thematic', or 'custom'"
    )
    clothing_rules: str = Field(
        default="Analyze the script to determine contextually appropriate clothing. Maintain consistency across scenes.",
        description="Rules for clothing selection and description"
    )
    opacity_rules: str = Field(
        default="100% opaque solid fabric. Completely hide body underneath. Real material, NOT transparent overlay.",
        description="Rules for clothing opacity"
    )
    max_pieces: int = Field(
        default=2,
        ge=1,
        le=5,
        description="Maximum number of clothing pieces allowed"
    )


class ProductionRules(BaseModel):
    """Production rules preset for cinematography and special cases."""
    
    id: Optional[str] = Field(default=None, description="Unique identifier")
    name: str = Field(description="Name of the production rules preset")
    camera_settings: str = Field(
        default="Medium or medium-wide shot, eye-level or chest-level camera, no extreme angles, same framing logic across scenes.",
        description="Default camera settings"
    )
    pose_rule: str = Field(
        default="Character's POSE, BODY POSITION, and GESTURE MUST change per scene. NEVER neutral standing pose. NEVER repeat identical poses.",
        description="Rules for character poses"
    )
    shot_intercalation_rules: str = Field(
        default="Minimum 4 different angles throughout video. Alternate: close, medium, wide, aerial. Never repeat framing consecutively.",
        description="Rules for shot variety"
    )
    death_scene_rules: str = Field(
        default="Overhead 100% camera, asymmetric fall posture (head NOT facing camera), pale blue-white bioluminescent translucent fluid pooling around body.",
        description="Rules for death scenes"
    )
    child_rules: str = Field(
        default="ALWAYS as small blurred indistinct human-shaped silhouette. NO facial features, NO detailed body. If infected: silhouette + green-grey coloring.",
        description="Rules for depicting children"
    )
    zombie_rules: str = Field(
        default="Infection communicated ONLY by: green-grey desaturated skin coloring, unnatural posture, empty eyes. ZERO wounds, ZERO decomposition, ZERO gore.",
        description="Rules for infected/zombie characters"
    )
    consequence_philosophy: str = Field(
        default="Show the CONSEQUENCE, not the symptom. Action REPRESENTS the concept. Always.",
        description="Philosophy for showing consequences"
    )
    scale_rule: str = Field(
        default="Character at identical human scale. In crowds, pushed and obscured. Never giant. Never artificially isolated.",
        description="Rules for character scale"
    )


# ---------------------------------------------------------------------------
# Product Placement / Merchandising
# ---------------------------------------------------------------------------

class ProductPlacement(BaseModel):
    """Product placement configuration for merchandising in videos."""
    
    enabled: bool = Field(default=False, description="Whether product placement is enabled")
    product_image_path: Optional[str] = Field(default=None, description="Path to the uploaded product image")
    product_image_base64: Optional[str] = Field(default=None, description="Base64 encoded product image for analysis")
    product_description: Optional[str] = Field(
        default=None, 
        description="AI-generated description of the product based on image analysis"
    )
    product_name: Optional[str] = Field(default=None, description="Name of the product (user-provided or AI-detected)")
    brand_name: Optional[str] = Field(default=None, description="Brand name if visible/known")
    placement_style: str = Field(
        default="natural",
        description="How to integrate the product: 'natural' (character uses it), 'prominent' (focus shots), 'subtle' (background)"
    )
    placement_frequency: str = Field(
        default="contextual",
        description="How often to show: 'contextual' (when story fits), 'frequent' (every few frames), 'key_moments' (climax only)"
    )


# ---------------------------------------------------------------------------
# Character Template
# ---------------------------------------------------------------------------

class CharacterTemplate(BaseModel):
    """Immutable character DNA – repeated verbatim in every image prompt."""

    name: str = Field(description="Short identifier for the character template")
    physical_description: str = Field(
        description="Full physical description of the character. "
        "This is injected verbatim into every image prompt."
    )
    style_keywords: list[str] = Field(
        default_factory=list,
        description="Visual style keywords applied to every prompt",
    )
    negative_keywords: list[str] = Field(
        default_factory=list,
        description="Keywords to explicitly exclude from every prompt",
    )
    hard_prohibitions: list[str] = Field(
        default_factory=list,
        description="Absolute prohibitions - things that MUST NEVER appear in any image. "
        "More strict than negative_keywords.",
    )
    camera_settings: str = Field(
        default="Medium or medium-wide shot, eye-level or chest-level camera, "
        "no extreme angles, no dramatic lens changes, same framing logic across scenes",
    )
    lighting_settings: str = Field(
        default="Real-world lighting matching the environment, natural shadows, "
        "photorealistic cinematic realism",
    )
    pose_rule: str = Field(
        default="The character's POSE, BODY POSITION, and GESTURE MUST change per "
        "scene to match the script. DO NOT keep a neutral standing pose unless "
        "the script explicitly implies it. DO NOT repeat identical poses in consecutive scenes.",
    )
    environment_rule: str = Field(
        default="Infer the environment directly from the script. Place the character "
        "naturally inside that environment. Environment must be realistic and "
        "context-appropriate. Vary environments across scenes for visual dynamism.",
    )
    clothing_rule: str = Field(
        default="Analyze the script to determine contextually appropriate clothing. "
        "ONE clothing style is defined at the start and maintained consistently across ALL scenes "
        "unless the script explicitly implies a change. Clothing must be REALISTIC and contextually believable. "
        "Always describe clothing in FULL DETAIL (color, fabric type, fit).",
    )
    anatomical_highlight_rules: str = Field(
        default="When the script mentions a BODY PART, ORGAN, HEALTH CONDITION, PAIN, or PHYSICAL SYMPTOM, "
        "that specific body area MUST be visually highlighted. Use distinct COLOR CODING:\n"
        "- PAIN/INFLAMMATION → Pulsing deep red or orange glow\n"
        "- FATIGUE/WEAKNESS → Faded blue-grey desaturation\n"
        "- BLOCKAGE/TENSION → Dense dark purple or black pressure cloud\n"
        "- DAMAGE/INJURY → Fractured visualization with amber-red highlight\n"
        "- HEALING/RECOVERY → Soft golden or green luminescent glow\n"
        "- NERVE SIGNAL/ACTIVITY → Electric blue or cyan pulse lines\n"
        "The highlight must look MEDICAL and SCIENTIFIC, not stylized or cartoonish.",
    )
    # New fields for enhanced prompt system (v2)
    consequence_philosophy: str = Field(
        default="",
        description="Philosophy for showing consequences instead of symptoms. "
        "Maps script concepts to representative actions.",
    )
    death_scene_rules: str = Field(
        default="",
        description="Rules for death scenes: camera angle, posture, visual elements.",
    )
    child_rules: str = Field(
        default="",
        description="Rules for depicting children: always abstract silhouettes.",
    )
    zombie_rules: str = Field(
        default="",
        description="Rules for infected/zombie characters: color + posture, no gore.",
    )
    shot_intercalation_rules: str = Field(
        default="",
        description="Rules for shot variety and intercalation throughout the video.",
    )


# ---------------------------------------------------------------------------
# Voice / Export settings
# ---------------------------------------------------------------------------

class VoiceSettings(BaseModel):
    # TTS Provider selection: "elevenlabs" (default) or "pollinations"
    tts_provider: str = Field(default="elevenlabs", description="TTS provider: 'elevenlabs' or 'pollinations'")
    
    # ElevenLabs settings
    voice_id: str = Field(default="JBFqnCBsd6RMkjVDRZzb", description="ElevenLabs voice ID")
    model_id: str = Field(default="eleven_multilingual_v2", description="ElevenLabs model ID")
    stability: float = Field(default=0.5, ge=0.0, le=1.0)
    similarity_boost: float = Field(default=0.75, ge=0.0, le=1.0)
    style: float = Field(default=0.0, ge=0.0, le=1.0)
    speed: float = Field(default=1.0, ge=0.5, le=2.0)
    
    # Pollinations TTS settings
    
    # Pollinations TTS settings (free cloud TTS)
    pollinations_model: str = Field(default="openai", description="TTS model: openai or elevenlabs")
    pollinations_voice: str = Field(default="nova", description="Pollinations voice name")
    pollinations_speed: float = Field(default=1.0, ge=0.25, le=4.0, description="Speech speed")
    pollinations_format: str = Field(default="mp3", description="Audio format: mp3, opus, aac, flac, wav, pcm")
    pollinations_api_key: Optional[str] = Field(default=None, description="Optional API key for higher limits")


class ExportSettings(BaseModel):
    resolution: str = Field(default="1080x1920")
    fps: int = Field(default=30)
    video_format: str = Field(default="mp4")
    aspect_ratio: str = Field(default="9:16")


# ---------------------------------------------------------------------------
# Scene models (produced by agents)
# ---------------------------------------------------------------------------

class SceneAnalysis(BaseModel):
    """Output of the Script Analyzer agent for a single scene."""

    scene_number: int = Field(description="Sequential scene number starting at 1")
    environment: str = Field(description="Physical setting / location of the scene")
    pose_action: str = Field(
        description="Character pose, body position and gesture for this scene"
    )
    mood: str = Field(description="Emotional tone / mood of the scene")
    narration_text: str = Field(
        description="Exact narration text to be spoken for this scene"
    )
    narration_notes: str = Field(
        default="",
        description="Optional delivery notes (pace, emotion, pauses)",
    )
    
    # Smart Coverage fields (AI-determined)
    coverage_priority: int = Field(
        default=5,
        ge=1,
        le=10,
        description="Importance score for coverage (1-10). Higher = more likely to get multi-angle coverage. "
        "10=climax/action, 7-9=emotional intensity, 4-6=normal, 1-3=transition/exposition"
    )
    needs_coverage: bool = Field(
        default=False,
        description="AI-determined: does this scene benefit from multi-angle coverage? "
        "True for action, emotion, climax. False for exposition, transition, static scenes."
    )


class SceneBreakdown(BaseModel):
    """Complete output of the Script Analyzer agent."""

    title: str = Field(description="Short title for the video")
    total_scenes: int = Field(description="Total number of scenes")
    scenes: list[SceneAnalysis] = Field(description="Ordered list of scenes")
    summary: str = Field(default="", description="Brief summary of the story")


# ---------------------------------------------------------------------------
# Frame models (produced by FrameExpander agent)
# ---------------------------------------------------------------------------

class Frame(BaseModel):
    """A single visual frame expanded from a narrative beat."""

    frame_number: int = Field(description="Global sequential frame number starting at 1")
    parent_scene: int = Field(description="Which narrative beat/scene this frame belongs to")
    camera_shot: str = Field(
        description="Camera shot type: wide shot, medium shot, medium close-up, "
        "close-up, extreme close-up, low angle, high angle, dutch angle, "
        "POV, over-the-shoulder, tracking shot, establishing shot"
    )
    camera_movement: str = Field(
        description="Camera movement: static, slow pan left/right, "
        "dolly forward/back, slight drift, tilt up/down, crane up"
    )
    composition: str = Field(
        description="Composition: centered, rule of thirds left/right, "
        "foreground emphasis, background depth, symmetrical, diagonal"
    )
    environment_detail: str = Field(
        description="Specific environment focus for this frame"
    )
    pose_action: str = Field(
        description="Specific character pose and action for this frame"
    )
    mood: str = Field(description="Emotional tone of this specific frame")
    visual_focus: str = Field(
        description="What the viewer's eye should be drawn to in this frame"
    )
    narration_text: str = Field(
        description="The narration text spoken during this frame's duration"
    )
    narration_notes: str = Field(
        default="",
        description="Delivery notes for this frame's narration",
    )
    sync_word: str = Field(
        default="",
        description="Exact word or short phrase (1-5 words) from the script that this frame represents. "
        "This is the editing anchor - the moment in the audio where this image should cut in.",
    )
    anatomical_highlight: str = Field(
        default="",
        description="If the frame involves a body part, organ, or health condition, describe the "
        "anatomical highlight with color coding (e.g., 'lower back - red/orange glow for pain'). "
        "Leave empty if no anatomical highlight is needed.",
    )
    
    # Coverage Shots metadata
    coverage_group_id: Optional[str] = Field(
        default=None,
        description="ID grouping frames that show the same scene from different angles. "
        "Frames with the same coverage_group_id represent the same moment with different camera angles.",
    )
    coverage_angle_index: int = Field(
        default=0,
        description="Index within coverage group: 0=master/wide, 1=medium, 2=close-up, 3+=variants",
    )
    coverage_angle_type: str = Field(
        default="",
        description="Type of coverage angle: 'master', 'medium', 'close-up', 'reverse', 'insert', or empty if not coverage",
    )


class ExpandedFrameBreakdown(BaseModel):
    """Output of the FrameExpander agent – all frames for the video."""

    title: str
    summary: str
    total_frames: int
    frames: list[Frame]


# ---------------------------------------------------------------------------
# Prompt models (produced by PromptEngineer – one per frame)
# ---------------------------------------------------------------------------

class ScenePrompts(BaseModel):
    """Output of the Prompt Engineer agent for a single frame."""

    scene_number: int
    image_prompt: str = Field(
        description="Full standalone image generation prompt with complete "
        "character description, environment, pose, camera, lighting, style"
    )
    video_prompt: str = Field(
        description="Image-to-video motion prompt describing character action "
        "joint-by-joint with kinetic chain, timing, and muscle visibility"
    )
    sync_word: str = Field(
        default="",
        description="Exact word or short phrase (1-5 words) from the script - "
        "the editing anchor where this image should cut in"
    )
    anatomical_highlight: str = Field(
        default="",
        description="Body part + real anatomical structure (e.g., 'quadriceps expanding with visible muscle fibers')"
    )
    clothing_description: str = Field(
        default="",
        description="Full clothing description for this scene (color, fabric, fit)"
    )
    environment: str = Field(
        default="",
        description="Environment/setting for this scene"
    )
    emotional_tone: str = Field(
        default="",
        description="Emotional tone of this scene (e.g., confusion, relief, fear)"
    )
    # New fields for enhanced video prompt system (v2)
    camera_move: str = Field(
        default="",
        description="Named camera movement from library (e.g., 'SLOW PUSH IN', 'ORBIT', 'PULL BACK REVEAL')"
    )
    character_action: str = Field(
        default="",
        description="Detailed character action joint-by-joint with kinetic chain, "
        "muscle visibility, timing in seconds, and transition to next scene"
    )
    sound_effects: str = Field(
        default="",
        description="Physical SFX for the scene (e.g., 'deep snow crunch per step, wood whistle'). "
        "NO music. NO voice."
    )
    # Narrative continuity fields (v3)
    ground_surface: str = Field(
        default="",
        description="Current ground/floor state (e.g., 'dry sand', 'wet sand with footprints', 'stormy ocean surface')"
    )
    accumulated_props: str = Field(
        default="",
        description="Comma-separated list of props visible in this scene with quantities "
        "(e.g., '5 coconut shells in pile, 12 tally marks on palm tree, complete raft nearby')"
    )
    companion_state: str = Field(
        default="",
        description="State of companion objects (e.g., 'coconut-friend: present near character' or 'coconut-friend: lost at sea')"
    )
    clothing_condition: str = Field(
        default="",
        description="Current clothing condition (e.g., 'clean and intact', 'dirty and worn', 'torn and ragged')"
    )


class BatchScenePrompts(BaseModel):
    """Output of a batched PromptEngineer call — prompts for multiple frames at once."""

    scenes: list[ScenePrompts] = Field(
        description="List of prompts, one per frame in the batch"
    )


class AllScenePrompts(BaseModel):
    """Complete collection of prompts for all frames."""

    scenes: list[ScenePrompts]


class SceneTTS(BaseModel):
    """Output of the TTS Scriptwriter agent for a single scene."""

    scene_number: int
    tts_text: str = Field(
        description="Exact text to send to ElevenLabs for voice generation"
    )
    voice_direction: str = Field(
        default="",
        description="Voice acting direction (tone, pace, emotion)",
    )


class AllSceneTTS(BaseModel):
    """Complete output of the TTS Scriptwriter agent."""

    scenes: list[SceneTTS]


# ---------------------------------------------------------------------------
# Timeline models
# ---------------------------------------------------------------------------

class SceneTimeline(BaseModel):
    """Timeline entry for a single scene."""

    scene_number: int
    time_start: str = Field(description="Start time in MM:SS format")
    time_end: str = Field(description="End time in MM:SS format")
    duration_seconds: float = Field(description="Duration in seconds")
    cut_point: str = Field(description="Exact cut point in MM:SS format")
    transition: str = Field(
        default="crossfade 0.3s",
        description="Transition type to next scene",
    )


class Timeline(BaseModel):
    """Complete timeline for the video."""

    total_duration_seconds: float
    total_duration_formatted: str = Field(description="Total duration in MM:SS")
    scenes: list[SceneTimeline]


# ---------------------------------------------------------------------------
# Production Plan (final output)
# ---------------------------------------------------------------------------

class ProductionScene(BaseModel):
    """A single frame with ALL production information combined."""

    scene_number: int
    time_start: str
    time_end: str
    duration_seconds: float
    cut_point: str
    transition: str
    environment: str
    pose_action: str
    mood: str
    camera_shot: str = Field(default="medium shot")
    camera_movement: str = Field(default="static")
    composition: str = Field(default="centered")
    visual_focus: str = Field(default="")
    image_prompt: str
    video_prompt: str
    tts_text: str
    voice_direction: str
    narration_notes: str
    sync_word: str = Field(default="", description="Editing anchor word/phrase from narration")
    anatomical_highlight: str = Field(default="", description="Body part + color if applicable")
    clothing_description: str = Field(default="", description="Full clothing description")
    emotional_tone: str = Field(default="", description="Emotional tone of this scene")
    
    # Coverage Shots metadata
    coverage_group_id: Optional[str] = Field(
        default=None,
        description="ID grouping frames that show the same scene from different angles"
    )
    coverage_angle_index: int = Field(
        default=0,
        description="Index within coverage group: 0=master, 1=medium, 2=close-up"
    )
    coverage_angle_type: str = Field(
        default="",
        description="Type of coverage angle: 'master', 'medium', 'close-up', etc."
    )


class ProductionPlan(BaseModel):
    """The complete production plan – the final output of the pipeline."""

    title: str
    summary: str
    total_duration_formatted: str
    total_duration_seconds: float
    total_scenes: int
    target_platform: str
    character_template_name: str
    voice_settings: VoiceSettings
    export_settings: ExportSettings
    scenes: list[ProductionScene]
    assembly_instructions: list[str] = Field(
        default_factory=list,
        description="Step-by-step instructions to assemble the final video",
    )


# ---------------------------------------------------------------------------
# Pipeline configuration
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# Pipeline event (WebSocket streaming)
# ---------------------------------------------------------------------------

class PipelineEvent(BaseModel):
    """Event emitted during pipeline execution for real-time UI updates."""

    event_type: str = Field(
        description="Type of event: agent_start, agent_complete, agent_progress, "
        "image_generated, pipeline_complete, error"
    )
    agent_number: Optional[int] = Field(default=None, description="Agent number 1-5")
    agent_name: Optional[str] = Field(default=None, description="Human-readable agent name")
    progress_current: Optional[int] = Field(default=None, description="Current progress count")
    progress_total: Optional[int] = Field(default=None, description="Total items to process")
    message: str = Field(default="", description="Human-readable status message")
    data: Optional[dict[str, Any]] = Field(default=None, description="Arbitrary event data")
    image_base64: Optional[str] = Field(default=None, description="Base64-encoded generated image")
    image_scene_number: Optional[int] = Field(default=None, description="Scene number for image")
    timestamp: str = Field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat(),
        description="ISO 8601 timestamp",
    )


# ---------------------------------------------------------------------------
# Pipeline configuration
# ---------------------------------------------------------------------------

class PipelineConfig(BaseModel):
    """Runtime configuration for the pipeline."""

    mode: PipelineMode = PipelineMode.PLAN
    llm_model: str = Field(default="openai/gpt-4o")
    target_platform: TargetPlatform = TargetPlatform.TIKTOK
    character_template: Optional[CharacterTemplate] = None
    
    # Decoupled style presets (optional - if not set, uses character_template rules)
    world_style: Optional[WorldStyle] = Field(
        default=None,
        description="World/environment style preset. If not set, uses character_template.environment_rule"
    )
    clothing_style: Optional[ClothingStyle] = Field(
        default=None,
        description="Clothing style preset. If not set, uses character_template.clothing_rule"
    )
    production_rules: Optional[ProductionRules] = Field(
        default=None,
        description="Production rules preset. If not set, uses character_template rules"
    )
    
    voice_settings: VoiceSettings = Field(default_factory=VoiceSettings)
    export_settings: ExportSettings = Field(default_factory=ExportSettings)
    output_dir: str = Field(default="output")
    speaking_rate_wps: float = Field(
        default=2.5,
        description="Words per second for duration estimation (default 150 wpm)",
    )
    min_scene_duration: float = Field(
        default=3.0,
        description="Minimum scene duration in seconds",
    )
    transition_duration: float = Field(
        default=0.3,
        description="Transition duration between scenes in seconds",
    )
    frames_per_minute: int = Field(
        default=25,
        description="Target number of image frames per minute of video",
    )
    
    # Coverage Shots - multi-angle cinematography
    enable_coverage_shots: bool = Field(
        default=False,
        description="Generate multiple camera angles for each scene (coverage shots)",
    )
    coverage_angles_per_scene: int = Field(
        default=3,
        ge=2,
        le=5,
        description="Number of camera angles to generate per scene (2-5)",
    )
    coverage_mode: str = Field(
        default="all",
        description="Coverage mode: 'all' (every scene) or 'smart' (AI decides key scenes)",
    )
    
    # Product Placement / Merchandising
    product_placement: Optional[ProductPlacement] = Field(
        default=None,
        description="Product placement configuration for merchandising in videos"
    )
