"""
Video Pipeline Mission Control - API Server

Serves pipeline data to the dashboard frontend.
Provides endpoints for:
- Channel listing with stats
- Opportunity browsing and filtering
- Production package details
- Job triggering (research, script generation)
"""

import json
import os
import re
import subprocess
import threading
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field, asdict
from enum import Enum

from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import uvicorn

# =============================================================================
# Configuration
# =============================================================================

OUTPUT_DIR = Path(__file__).parent / "output"
RESEARCH_SERVICE_URL = "http://127.0.0.1:8100"

# Channel files pattern
CHANNEL_FILE_PATTERN = re.compile(r'analysis_opportunities_([a-z_]+)\.json$')

# =============================================================================
# Data Models
# =============================================================================

class Stage(str, Enum):
    DISCOVERED = "DISCOVERED"
    ANALYZED = "ANALYZED"
    RESEARCHED = "RESEARCHED"
    SCRIPTED = "SCRIPTED"
    PRODUCTION_READY = "PRODUCTION_READY"

class JobType(str, Enum):
    RESEARCH = "RESEARCH"
    SCRIPT = "SCRIPT"

class JobStatus(str, Enum):
    PENDING = "PENDING"
    RUNNING = "RUNNING"
    COMPLETED = "COMPLETED"
    FAILED = "FAILED"

# Pydantic models for API responses
class ChannelStats(BaseModel):
    total: int
    high_score: int  # 4.5+
    researched: int
    scripted: int
    ready: int
    finished: int  # Actual completed products in channel folder
    avg_score: float

class Channel(BaseModel):
    id: str
    name: str
    description: Optional[str] = None
    stats: ChannelStats

class ScoreBreakdown(BaseModel):
    scroll_stop_power: int
    completion_probability: int
    share_save_potential: int
    demand_signal: int
    visual_potential: int
    evergreen_potential: int

class OpportunitySummary(BaseModel):
    id: str
    index: int
    channel_id: str
    title: str
    premise: str
    stage: Stage
    weighted_score: float
    verdict: Optional[str] = None
    research_word_count: Optional[int] = None
    script_score: Optional[float] = None
    hooks: List[str] = []
    scores: Optional[ScoreBreakdown] = None
    last_updated: Optional[str] = None

class Job(BaseModel):
    id: str
    type: JobType
    channel_id: str
    opportunity_index: int
    target_name: str
    status: JobStatus
    started_at: str
    duration_seconds: Optional[float] = None
    error: Optional[str] = None

class ResearchRequest(BaseModel):
    channel_id: str
    opportunity_index: int

class ScriptRequest(BaseModel):
    channel_id: str
    opportunity_index: int

# =============================================================================
# In-Memory Job Tracking
# =============================================================================

@dataclass
class JobTracker:
    jobs: Dict[str, Dict] = field(default_factory=dict)
    _lock: threading.Lock = field(default_factory=threading.Lock)
    _counter: int = 0

    def create_job(self, job_type: JobType, channel_id: str, opportunity_index: int, target_name: str) -> str:
        with self._lock:
            self._counter += 1
            job_id = f"job_{self._counter}_{int(time.time())}"
            self.jobs[job_id] = {
                "id": job_id,
                "type": job_type,
                "channel_id": channel_id,
                "opportunity_index": opportunity_index,
                "target_name": target_name,
                "status": JobStatus.PENDING,
                "started_at": datetime.now().isoformat(),
                "duration_seconds": None,
                "error": None
            }
            return job_id

    def update_status(self, job_id: str, status: JobStatus, error: Optional[str] = None):
        with self._lock:
            if job_id in self.jobs:
                self.jobs[job_id]["status"] = status
                if error:
                    self.jobs[job_id]["error"] = error
                if status in [JobStatus.COMPLETED, JobStatus.FAILED]:
                    started = datetime.fromisoformat(self.jobs[job_id]["started_at"])
                    self.jobs[job_id]["duration_seconds"] = (datetime.now() - started).total_seconds()

    def get_job(self, job_id: str) -> Optional[Dict]:
        return self.jobs.get(job_id)

    def get_active_jobs(self) -> List[Dict]:
        return [j for j in self.jobs.values() if j["status"] in [JobStatus.PENDING, JobStatus.RUNNING]]

    def get_recent_jobs(self, limit: int = 20) -> List[Dict]:
        sorted_jobs = sorted(self.jobs.values(), key=lambda x: x["started_at"], reverse=True)
        return sorted_jobs[:limit]

job_tracker = JobTracker()

# =============================================================================
# Data Loading Functions
# =============================================================================

def get_channel_files() -> Dict[str, Path]:
    """Find all channel analysis files."""
    channels = {}
    for file in OUTPUT_DIR.glob("analysis_opportunities_*.json"):
        match = CHANNEL_FILE_PATTERN.match(file.name)
        if match:
            channel_id = match.group(1)
            # Skip timestamped backup files
            if not re.search(r'_\d{8}_\d{6}\.json$', file.name):
                channels[channel_id] = file
    return channels

def load_channel_data(channel_id: str) -> Optional[Dict]:
    """Load opportunities data for a channel."""
    file_path = OUTPUT_DIR / f"analysis_opportunities_{channel_id}.json"
    if not file_path.exists():
        return None

    try:
        with open(file_path, 'r') as f:
            return json.load(f)
    except json.JSONDecodeError:
        return None

def get_finished_products(channel_id: str = None) -> Dict[str, List[Dict]]:
    """
    Get finished products from channel folders.

    Finished products are stored in output/{channel_id}/*.json
    Returns dict mapping channel_id to list of finished product metadata.
    """
    finished = {}

    # Get all channel folders in output directory
    for item in OUTPUT_DIR.iterdir():
        if item.is_dir() and not item.name.startswith('.') and not item.name.startswith('__'):
            # Skip if we're filtering to a specific channel
            if channel_id and item.name != channel_id:
                continue

            channel_finished = []
            for json_file in item.glob('*.json'):
                try:
                    with open(json_file) as f:
                        data = json.load(f)
                    # Finished products have meta.production_ready = True
                    meta = data.get('meta', {})
                    if meta.get('production_ready'):
                        channel_finished.append({
                            'filename': json_file.name,
                            'premise': meta.get('premise', ''),
                            'generated_at': meta.get('generated_at', ''),
                            'final_score': meta.get('final_score', 0),
                            'channel_id': item.name
                        })
                except (json.JSONDecodeError, IOError):
                    continue

            if channel_finished:
                finished[item.name] = channel_finished

    return finished

def count_all_finished_products() -> Dict[str, int]:
    """Count finished products per channel and total."""
    finished = get_finished_products()
    counts = {channel: len(products) for channel, products in finished.items()}
    counts['_total'] = sum(counts.values())
    return counts

def determine_stage(opp: Dict) -> Stage:
    """Determine the pipeline stage of an opportunity."""
    if opp.get("production_ready"):
        return Stage.PRODUCTION_READY
    if opp.get("script") or opp.get("evaluation"):
        return Stage.SCRIPTED
    if opp.get("research_completed") or opp.get("research_report_content"):
        return Stage.RESEARCHED
    if opp.get("weighted_score") is not None:
        return Stage.ANALYZED
    return Stage.DISCOVERED

def get_channel_stats(opportunities: List[Dict], channel_id: str = None) -> ChannelStats:
    """Calculate stats for a list of opportunities."""
    total = len(opportunities)
    high_score = sum(1 for o in opportunities if (o.get("weighted_score") or 0) >= 4.5)
    researched = sum(1 for o in opportunities if o.get("research_completed") or o.get("research_report_content"))
    scripted = sum(1 for o in opportunities if o.get("script") or o.get("evaluation"))
    ready = sum(1 for o in opportunities if o.get("production_ready"))

    # Count finished products from channel folder
    finished = 0
    if channel_id:
        finished_products = get_finished_products(channel_id)
        finished = len(finished_products.get(channel_id, []))

    scores = [o.get("weighted_score", 0) for o in opportunities if o.get("weighted_score")]
    avg_score = sum(scores) / len(scores) if scores else 0

    return ChannelStats(
        total=total,
        high_score=high_score,
        researched=researched,
        scripted=scripted,
        ready=ready,
        finished=finished,
        avg_score=round(avg_score, 2)
    )

def opportunity_to_summary(opp: Dict, index: int, channel_id: str) -> OpportunitySummary:
    """Convert raw opportunity dict to summary model."""
    scores = opp.get("scores", {})
    score_breakdown = None
    if scores:
        score_breakdown = ScoreBreakdown(
            scroll_stop_power=scores.get("scroll_stop_power", {}).get("score", 0) if isinstance(scores.get("scroll_stop_power"), dict) else scores.get("scroll_stop_power", 0),
            completion_probability=scores.get("completion_probability", {}).get("score", 0) if isinstance(scores.get("completion_probability"), dict) else scores.get("completion_probability", 0),
            share_save_potential=scores.get("share_save_potential", {}).get("score", 0) if isinstance(scores.get("share_save_potential"), dict) else scores.get("share_save_potential", 0),
            demand_signal=scores.get("demand_signal", {}).get("score", 0) if isinstance(scores.get("demand_signal"), dict) else scores.get("demand_signal", 0),
            visual_potential=scores.get("visual_potential", {}).get("score", 0) if isinstance(scores.get("visual_potential"), dict) else scores.get("visual_potential", 0),
            evergreen_potential=scores.get("evergreen_potential", {}).get("score", 0) if isinstance(scores.get("evergreen_potential"), dict) else scores.get("evergreen_potential", 0)
        )

    # Get hooks - may be strings or dicts with 'opening_hook' key
    raw_hooks = opp.get("hook_candidates", [])
    hooks = []
    for h in raw_hooks:
        if isinstance(h, str):
            hooks.append(h)
        elif isinstance(h, dict):
            # Extract the hook text from dict format
            hook_text = h.get("opening_hook") or h.get("hook") or h.get("source_post_title", "")
            if hook_text:
                hooks.append(hook_text)

    # Add opening_hook at the front if present
    opening_hook = opp.get("opening_hook")
    if opening_hook and isinstance(opening_hook, str):
        hooks = [opening_hook] + [h for h in hooks if h != opening_hook]

    # Research word count
    research_word_count = opp.get("research_word_count")
    if not research_word_count and opp.get("research_report_content"):
        research_word_count = len(opp["research_report_content"].split())

    # Script score
    script_score = None
    if opp.get("evaluation"):
        script_score = opp["evaluation"].get("composite_score")

    return OpportunitySummary(
        id=f"{channel_id}-{index}",
        index=index,
        channel_id=channel_id,
        title=opp.get("suggested_title", opp.get("cluster_label", "Untitled")),
        premise=opp.get("premise", opp.get("description", "")),
        stage=determine_stage(opp),
        weighted_score=opp.get("weighted_score", 0),
        verdict=opp.get("verdict"),
        research_word_count=research_word_count,
        script_score=script_score,
        hooks=hooks[:3],
        scores=score_breakdown
    )

def load_production_package(channel_id: str, opportunity: Dict) -> Optional[Dict]:
    """Load production package for an opportunity if it exists."""
    channel_dir = OUTPUT_DIR / channel_id
    if not channel_dir.exists():
        return None

    # Look for matching production file
    title = opportunity.get("suggested_title", "")
    for file in channel_dir.glob("*.json"):
        try:
            with open(file, 'r') as f:
                pkg = json.load(f)
                # Match by cluster_id or title
                if pkg.get("input", {}).get("cluster_id") == opportunity.get("cluster_id"):
                    return pkg
                if pkg.get("meta", {}).get("premise", "").lower() in title.lower():
                    return pkg
        except:
            continue

    return None

# =============================================================================
# Background Job Runners
# =============================================================================

def run_research_job(job_id: str, channel_id: str, opportunity_index: int):
    """Run research for an opportunity in the background."""
    import requests

    job_tracker.update_status(job_id, JobStatus.RUNNING)

    try:
        # Load opportunity data
        data = load_channel_data(channel_id)
        if not data or opportunity_index >= len(data.get("opportunities", [])):
            raise ValueError(f"Opportunity not found: {channel_id}[{opportunity_index}]")

        opp = data["opportunities"][opportunity_index]
        query = opp.get("suggested_title", opp.get("premise", ""))

        # Submit to research service
        response = requests.post(
            f"{RESEARCH_SERVICE_URL}/research",
            json={
                "query": query,
                "report_type": "research_report",
                "max_results": 30,
                "timeout": 600
            },
            timeout=30
        )

        if response.status_code != 200:
            raise Exception(f"Research service error: {response.text}")

        result = response.json()
        research_id = result.get("job_id")

        # Poll for completion
        while True:
            status_response = requests.get(f"{RESEARCH_SERVICE_URL}/research/{research_id}", timeout=30)
            status = status_response.json()

            if status.get("status") == "completed" or status.get("success"):
                # Save research to opportunity
                research_content = status.get("report", status.get("result", {}).get("report", ""))
                opp["research_report_content"] = research_content
                opp["research_completed"] = True
                opp["research_word_count"] = len(research_content.split())
                opp["research_completed_at"] = datetime.now().isoformat()

                # Write back
                file_path = OUTPUT_DIR / f"analysis_opportunities_{channel_id}.json"
                with open(file_path, 'w') as f:
                    json.dump(data, f, indent=2)

                job_tracker.update_status(job_id, JobStatus.COMPLETED)
                return

            if status.get("status") == "failed" or status.get("error"):
                raise Exception(status.get("error", "Research failed"))

            time.sleep(5)

    except Exception as e:
        job_tracker.update_status(job_id, JobStatus.FAILED, str(e))

def run_script_job(job_id: str, channel_id: str, opportunity_index: int):
    """Run script generation for an opportunity in the background."""
    job_tracker.update_status(job_id, JobStatus.RUNNING)

    try:
        # Import video generation module
        import sys
        sys.path.insert(0, str(Path(__file__).parent))
        from video_generation import layer4_run_director_evaluator_chain

        # Load opportunity data
        data = load_channel_data(channel_id)
        if not data or opportunity_index >= len(data.get("opportunities", [])):
            raise ValueError(f"Opportunity not found: {channel_id}[{opportunity_index}]")

        opp = data["opportunities"][opportunity_index]

        # Check if researched
        if not opp.get("research_completed") and not opp.get("research_report_content"):
            raise ValueError("Opportunity must be researched before script generation")

        # Run Layer 4
        result = layer4_run_director_evaluator_chain(opp, channel_id=channel_id)

        if result.get("success"):
            # Update opportunity with results
            opp["script"] = result.get("script")
            opp["evaluation"] = result.get("evaluation")
            opp["production_ready"] = result.get("evaluation", {}).get("verdict") == "PRODUCTION_READY"
            opp["all_iterations"] = result.get("all_iterations")
            opp["layer4_completed_at"] = datetime.now().isoformat()

            # Write back
            file_path = OUTPUT_DIR / f"analysis_opportunities_{channel_id}.json"
            with open(file_path, 'w') as f:
                json.dump(data, f, indent=2)

            job_tracker.update_status(job_id, JobStatus.COMPLETED)
        else:
            raise Exception(result.get("error", "Script generation failed"))

    except Exception as e:
        job_tracker.update_status(job_id, JobStatus.FAILED, str(e))

# =============================================================================
# FastAPI App
# =============================================================================

app = FastAPI(
    title="Video Pipeline Mission Control API",
    description="Backend API for the video content pipeline dashboard",
    version="1.0.0"
)

# CORS for frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# =============================================================================
# API Endpoints
# =============================================================================

@app.get("/api/health")
async def health_check():
    """System health check."""
    import requests

    research_status = "offline"
    try:
        resp = requests.get(f"{RESEARCH_SERVICE_URL}/health", timeout=5)
        if resp.status_code == 200:
            research_status = "online"
    except:
        pass

    return {
        "status": "healthy",
        "timestamp": datetime.now().isoformat(),
        "services": {
            "api": "online",
            "research": research_status
        }
    }

@app.get("/api/stats")
async def pipeline_stats():
    """Get pipeline-wide statistics."""
    channels = get_channel_files()

    total_opportunities = 0
    total_analyzed = 0
    total_researched = 0
    total_scripted = 0
    total_ready = 0
    all_scores = []

    for channel_id, file_path in channels.items():
        data = load_channel_data(channel_id)
        if data and "opportunities" in data:
            opps = data["opportunities"]
            total_opportunities += len(opps)
            for opp in opps:
                if opp.get("weighted_score") is not None:
                    total_analyzed += 1
                    all_scores.append(opp["weighted_score"])
                if opp.get("research_completed") or opp.get("research_report_content"):
                    total_researched += 1
                if opp.get("script") or opp.get("evaluation"):
                    total_scripted += 1
                if opp.get("production_ready"):
                    total_ready += 1

    # Count finished products from channel folders (the actual completed videos)
    finished_counts = count_all_finished_products()
    total_finished = finished_counts.get('_total', 0)
    channels_with_finished = {k: v for k, v in finished_counts.items() if k != '_total'}

    avg_score = sum(all_scores) / len(all_scores) if all_scores else 0

    return {
        "total_channels": len(channels),
        "total_opportunities": total_opportunities,
        "analyzed": total_analyzed,
        "analyzed_pct": round(total_analyzed / total_opportunities * 100, 1) if total_opportunities else 0,
        "researched": total_researched,
        "researched_pct": round(total_researched / total_opportunities * 100, 1) if total_opportunities else 0,
        "scripted": total_scripted,
        "scripted_pct": round(total_scripted / total_opportunities * 100, 1) if total_opportunities else 0,
        "production_ready": total_ready,
        "ready_pct": round(total_ready / total_opportunities * 100, 1) if total_opportunities else 0,
        # Finished products - actual completed videos in channel folders
        "finished": total_finished,
        "finished_pct": round(total_finished / total_opportunities * 100, 1) if total_opportunities else 0,
        "finished_by_channel": channels_with_finished,
        "avg_score": round(avg_score, 2)
    }

@app.get("/api/channels", response_model=List[Channel])
async def list_channels():
    """List all channels with their statistics."""
    channels = get_channel_files()
    result = []

    for channel_id, file_path in sorted(channels.items()):
        data = load_channel_data(channel_id)
        if data and "opportunities" in data:
            opps = data["opportunities"]
            stats = get_channel_stats(opps, channel_id)

            # Try to get channel description from metadata
            description = data.get("channel_description", data.get("description"))

            result.append(Channel(
                id=channel_id,
                name=channel_id.replace("_", " ").title(),
                description=description,
                stats=stats
            ))

    # Sort by average score descending
    result.sort(key=lambda x: x.stats.avg_score, reverse=True)
    return result

@app.get("/api/channels/{channel_id}/opportunities")
async def list_opportunities(
    channel_id: str,
    stage: Optional[Stage] = None,
    min_score: float = 0,
    max_score: float = 5,
    search: Optional[str] = None,
    limit: int = 100,
    offset: int = 0
):
    """List opportunities for a channel with filtering."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])

    # Convert to summaries
    summaries = [opportunity_to_summary(opp, i, channel_id) for i, opp in enumerate(opportunities)]

    # Apply filters
    if stage:
        summaries = [s for s in summaries if s.stage == stage]

    summaries = [s for s in summaries if min_score <= s.weighted_score <= max_score]

    if search:
        search_lower = search.lower()
        summaries = [s for s in summaries if search_lower in s.title.lower() or search_lower in s.premise.lower()]

    # Sort by score descending
    summaries.sort(key=lambda x: x.weighted_score, reverse=True)

    # Pagination
    total = len(summaries)
    summaries = summaries[offset:offset + limit]

    return {
        "channel_id": channel_id,
        "total": total,
        "offset": offset,
        "limit": limit,
        "opportunities": [s.dict() for s in summaries]
    }

@app.get("/api/opportunities/{channel_id}/{index}")
async def get_opportunity(channel_id: str, index: int):
    """Get full opportunity details including script and evaluation."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]
    summary = opportunity_to_summary(opp, index, channel_id)

    # Build full response
    response = summary.dict()

    # Add full data
    response["full_scores"] = opp.get("scores", {})
    response["core_reveal"] = opp.get("core_reveal")
    response["first_frame"] = opp.get("first_frame")
    response["structure"] = opp.get("structure")
    response["target_audience"] = opp.get("target_audience")
    response["all_hooks"] = opp.get("hook_candidates", [])

    # Research
    if opp.get("research_report_content"):
        response["research"] = {
            "content": opp["research_report_content"],
            "word_count": len(opp["research_report_content"].split()),
            "completed_at": opp.get("research_completed_at")
        }

    # Script
    if opp.get("script"):
        response["script"] = opp["script"]

    # Evaluation
    if opp.get("evaluation"):
        response["evaluation"] = opp["evaluation"]

    # All iterations
    if opp.get("all_iterations"):
        response["iterations"] = opp["all_iterations"]

    # Narration script
    if opp.get("narration_script"):
        response["narration_script"] = opp["narration_script"]

    # Production package
    pkg = load_production_package(channel_id, opp)
    if pkg:
        response["production_package"] = {
            "available": True,
            "meta": pkg.get("meta"),
            "narration_script": pkg.get("narration_script")
        }

    return response

@app.get("/api/opportunities/{channel_id}/{index}/research")
async def get_opportunity_research(channel_id: str, index: int):
    """Get research content for an opportunity."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]

    if not opp.get("research_report_content"):
        raise HTTPException(status_code=404, detail="Research not available")

    return {
        "content": opp["research_report_content"],
        "word_count": len(opp["research_report_content"].split()),
        "completed_at": opp.get("research_completed_at")
    }

@app.get("/api/opportunities/{channel_id}/{index}/script")
async def get_opportunity_script(channel_id: str, index: int):
    """Get script and evaluation for an opportunity."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]

    if not opp.get("script"):
        raise HTTPException(status_code=404, detail="Script not available")

    return {
        "script": opp["script"],
        "evaluation": opp.get("evaluation"),
        "narration_script": opp.get("narration_script"),
        "all_iterations": opp.get("all_iterations"),
        "production_ready": opp.get("production_ready", False)
    }

# =============================================================================
# Job Management Endpoints
# =============================================================================

@app.get("/api/jobs")
async def list_jobs(active_only: bool = False):
    """List jobs."""
    if active_only:
        jobs = job_tracker.get_active_jobs()
    else:
        jobs = job_tracker.get_recent_jobs()

    return {"jobs": jobs}

@app.get("/api/jobs/{job_id}")
async def get_job(job_id: str):
    """Get job status."""
    job = job_tracker.get_job(job_id)
    if not job:
        raise HTTPException(status_code=404, detail="Job not found")
    return job

@app.post("/api/opportunities/{channel_id}/{index}/research")
async def trigger_research(channel_id: str, index: int, background_tasks: BackgroundTasks):
    """Trigger research for an opportunity."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]

    # Check if already researched
    if opp.get("research_completed") or opp.get("research_report_content"):
        return {"message": "Already researched", "status": "skipped"}

    # Create job
    target_name = opp.get("suggested_title", opp.get("premise", "Unknown"))[:50]
    job_id = job_tracker.create_job(JobType.RESEARCH, channel_id, index, target_name)

    # Run in background
    background_tasks.add_task(run_research_job, job_id, channel_id, index)

    return {"job_id": job_id, "status": "started"}

@app.post("/api/opportunities/{channel_id}/{index}/script")
async def trigger_script(channel_id: str, index: int, background_tasks: BackgroundTasks):
    """Trigger script generation for an opportunity."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]

    # Check if researched
    if not opp.get("research_completed") and not opp.get("research_report_content"):
        raise HTTPException(status_code=400, detail="Must research before generating script")

    # Create job
    target_name = opp.get("suggested_title", opp.get("premise", "Unknown"))[:50]
    job_id = job_tracker.create_job(JobType.SCRIPT, channel_id, index, target_name)

    # Run in background
    background_tasks.add_task(run_script_job, job_id, channel_id, index)

    return {"job_id": job_id, "status": "started"}

@app.post("/api/batch/research")
async def batch_research(requests: List[ResearchRequest], background_tasks: BackgroundTasks):
    """Trigger research for multiple opportunities."""
    job_ids = []

    for req in requests:
        data = load_channel_data(req.channel_id)
        if not data:
            continue

        opportunities = data.get("opportunities", [])
        if req.opportunity_index < 0 or req.opportunity_index >= len(opportunities):
            continue

        opp = opportunities[req.opportunity_index]

        # Skip if already researched
        if opp.get("research_completed") or opp.get("research_report_content"):
            continue

        target_name = opp.get("suggested_title", opp.get("premise", "Unknown"))[:50]
        job_id = job_tracker.create_job(JobType.RESEARCH, req.channel_id, req.opportunity_index, target_name)
        background_tasks.add_task(run_research_job, job_id, req.channel_id, req.opportunity_index)
        job_ids.append(job_id)

    return {"job_ids": job_ids, "count": len(job_ids)}

# =============================================================================
# Production Export Endpoints
# =============================================================================

@app.get("/api/opportunities/{channel_id}/{index}/export/narration")
async def export_narration(channel_id: str, index: int):
    """Export narration script as plain text."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]

    if not opp.get("script"):
        raise HTTPException(status_code=404, detail="Script not available")

    # Extract narration from beats
    script = opp["script"]
    narration_lines = []

    for beat in script.get("beats", []):
        for segment in beat.get("segments", []):
            if segment.get("voiceover"):
                narration_lines.append(segment["voiceover"])

    narration = "\n\n".join(narration_lines)

    return {
        "title": opp.get("suggested_title", "Untitled"),
        "narration": narration,
        "word_count": len(narration.split())
    }

@app.get("/api/opportunities/{channel_id}/{index}/export/package")
async def export_package(channel_id: str, index: int):
    """Export full production package."""
    data = load_channel_data(channel_id)
    if not data:
        raise HTTPException(status_code=404, detail=f"Channel not found: {channel_id}")

    opportunities = data.get("opportunities", [])
    if index < 0 or index >= len(opportunities):
        raise HTTPException(status_code=404, detail=f"Opportunity not found: {index}")

    opp = opportunities[index]

    # Build package
    package = {
        "meta": {
            "channel": channel_id,
            "title": opp.get("suggested_title"),
            "premise": opp.get("premise"),
            "weighted_score": opp.get("weighted_score"),
            "exported_at": datetime.now().isoformat()
        },
        "input": {
            "hooks": opp.get("hook_candidates", []),
            "scores": opp.get("scores"),
            "core_reveal": opp.get("core_reveal"),
            "structure": opp.get("structure")
        }
    }

    if opp.get("research_report_content"):
        package["research"] = {
            "content": opp["research_report_content"],
            "word_count": len(opp["research_report_content"].split())
        }

    if opp.get("script"):
        package["script"] = opp["script"]

    if opp.get("evaluation"):
        package["evaluation"] = opp["evaluation"]

    return package

# =============================================================================
# Entry Point
# =============================================================================

if __name__ == "__main__":
    print("Starting Video Pipeline Mission Control API...")
    print(f"Output directory: {OUTPUT_DIR}")
    print(f"Research service: {RESEARCH_SERVICE_URL}")
    uvicorn.run(app, host="0.0.0.0", port=8200)
