"""
Video Production Pipeline – HTTP API CLI.

Standalone CLI that wraps ALL API endpoints via httpx.
No web UI needed — full automation from the terminal.

Usage:
    python cli.py projects list
    python cli.py generate images PROJECT_ID
    python cli.py export video PROJECT_ID
    python cli.py --help
"""

from __future__ import annotations

import argparse
import asyncio
import json
import sys
from pathlib import Path
from typing import Any

import httpx
from rich.console import Console
from rich.panel import Panel
from rich.progress import (
    BarColumn,
    MofNCompleteColumn,
    Progress,
    SpinnerColumn,
    TextColumn,
    TimeElapsedColumn,
)
from rich.table import Table
from rich.text import Text
from rich import box

# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------

console = Console()
DEFAULT_API_URL = "http://localhost:8000"

# ---------------------------------------------------------------------------
# HTTP helpers
# ---------------------------------------------------------------------------


def _client(api_url: str, timeout: float = 30.0) -> httpx.AsyncClient:
    """Create an async HTTP client with base URL."""
    return httpx.AsyncClient(base_url=api_url, timeout=timeout)


async def _request(
    client: httpx.AsyncClient,
    method: str,
    path: str,
    *,
    json_body: Any = None,
    params: dict[str, Any] | None = None,
) -> httpx.Response:
    """Make an HTTP request with standardised error handling."""
    try:
        resp = await client.request(method, path, json=json_body, params=params)
    except httpx.ConnectError:
        console.print(
            f"[red bold]Cannot connect to API at {client.base_url}.[/red bold]\n"
            "[dim]Is the server running? Start it with: python run_web.py[/dim]"
        )
        sys.exit(1)
    except httpx.TimeoutException:
        console.print("[red bold]Request timed out.[/red bold]")
        sys.exit(1)

    if resp.status_code == 404:
        data = _safe_json(resp)
        msg = data.get("error", "Not found") if isinstance(data, dict) else "Not found"
        console.print(f"[red]{msg}[/red]")
        sys.exit(1)
    if resp.status_code >= 500:
        data = _safe_json(resp)
        msg = data.get("error", resp.text) if isinstance(data, dict) else resp.text
        console.print(f"[red bold]Server error ({resp.status_code}):[/red bold] {msg}")
        sys.exit(1)
    if resp.status_code >= 400:
        data = _safe_json(resp)
        msg = data.get("error", resp.text) if isinstance(data, dict) else resp.text
        console.print(f"[yellow]Error ({resp.status_code}):[/yellow] {msg}")
        sys.exit(1)

    return resp


def _safe_json(resp: httpx.Response) -> Any:
    """Parse JSON from response, return empty dict on failure."""
    try:
        return resp.json()
    except Exception:
        return {}


def _print_json(data: Any) -> None:
    """Print raw JSON for --json mode."""
    console.print_json(json.dumps(data, indent=2, default=str, ensure_ascii=False))


# ---------------------------------------------------------------------------
# SSE streaming helper
# ---------------------------------------------------------------------------


async def _stream_sse(
    client: httpx.AsyncClient,
    method: str,
    path: str,
    *,
    label: str = "Generating",
    json_mode: bool = False,
) -> list[dict[str, Any]]:
    """Stream an SSE endpoint, displaying a rich progress bar.

    Returns the list of all received SSE data payloads.
    """
    events: list[dict[str, Any]] = []

    try:
        req = client.build_request(method, path)
        resp = await client.send(req, stream=True)
    except httpx.ConnectError:
        console.print(
            f"[red bold]Cannot connect to API at {client.base_url}.[/red bold]\n"
            "[dim]Is the server running?[/dim]"
        )
        sys.exit(1)

    if resp.status_code >= 400:
        body = await resp.aread()
        data = {}
        try:
            data = json.loads(body)
        except Exception:
            pass
        msg = (
            data.get("error", body.decode(errors="replace"))
            if isinstance(data, dict)
            else body.decode(errors="replace")
        )
        console.print(f"[red]Error ({resp.status_code}):[/red] {msg}")
        sys.exit(1)

    total: int | None = None
    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        BarColumn(),
        MofNCompleteColumn(),
        TimeElapsedColumn(),
        console=console,
        disable=json_mode,
    ) as progress:
        task_id = progress.add_task(label, total=None)

        async for raw_line in resp.aiter_lines():
            line = raw_line.strip()
            if line.startswith("event: done"):
                break
            if not line.startswith("data: "):
                continue

            try:
                data = json.loads(line[6:])
            except json.JSONDecodeError:
                continue

            events.append(data)

            # Update total on first payload that has it
            if total is None:
                t = data.get("total")
                if t:
                    total = int(t)
                    progress.update(task_id, total=total)

            scene = data.get("scene_number", "?")
            status = data.get("status", "")
            current = data.get("current") or data.get("progress")

            if status == "generating":
                progress.update(task_id, description=f"{label} scene {scene}...")
            elif status in ("done", "generated"):
                if current is not None:
                    progress.update(task_id, completed=int(current))
                if not json_mode:
                    console.print(f"  [green]✓[/green] Scene {scene} done")
            elif status == "error" or data.get("error"):
                err = data.get("error", "unknown error")
                if not json_mode:
                    console.print(f"  [red]✗[/red] Scene {scene}: {err}")

    await resp.aclose()

    if json_mode:
        _print_json(events)

    return events


# ═══════════════════════════════════════════════════════════════════════════
# Command implementations
# ═══════════════════════════════════════════════════════════════════════════


# ---------------------------------------------------------------------------
# projects
# ---------------------------------------------------------------------------


async def cmd_projects_list(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "GET", "/api/projects")
        data = resp.json()

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

    if args.json:
        _print_json(data)
        return

    if not projects:
        console.print("[dim]No projects found.[/dim]")
        return

    table = Table(title="Projects", box=box.ROUNDED, show_lines=True)
    table.add_column("ID", style="cyan", no_wrap=True)
    table.add_column("Title", style="bold")
    table.add_column("Status", justify="center")
    table.add_column("Character")
    table.add_column("Scenes", justify="right")
    table.add_column("Created")

    for p in projects:
        status = p.get("status", "")
        style = {"completed": "green", "running": "yellow", "error": "red"}.get(
            status, "dim"
        )
        scenes = p.get("result", {})
        scene_count = (
            str(len(scenes.get("scenes", []))) if isinstance(scenes, dict) else "-"
        )
        table.add_row(
            str(p.get("id", "")),
            p.get("title") or p.get("story_preview", "")[:40] or "-",
            Text(status, style=style),
            p.get("character", "-"),
            scene_count,
            str(p.get("created_at", ""))[:19],
        )

    console.print(table)


async def cmd_projects_show(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "GET", f"/api/projects/{args.project_id}")
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    # Header panel
    title = data.get("title") or data.get("story_preview", "")[:60] or "Untitled"
    status = data.get("status", "unknown")
    console.print(
        Panel(
            f"[bold]{title}[/bold]\n"
            f"ID: [cyan]{data.get('id')}[/cyan]   Status: [{'green' if status == 'completed' else 'yellow'}]{status}[/]\n"
            f"Character: {data.get('character', '-')}   Platform: {data.get('platform', '-')}",
            title="[bold cyan]Project Details[/bold cyan]",
            border_style="cyan",
        )
    )

    # Scenes table
    result = data.get("result", {})
    scenes = result.get("scenes", []) if isinstance(result, dict) else []
    if scenes:
        table = Table(title="Scenes", box=box.SIMPLE_HEAVY)
        table.add_column("#", justify="right", style="bold")
        table.add_column("Description", max_width=50)
        table.add_column("Duration", justify="right")
        table.add_column("TTS Text", max_width=40)

        for s in scenes:
            table.add_row(
                str(s.get("scene_number", "")),
                (s.get("environment_description") or s.get("setting", ""))[:50],
                f"{s.get('duration_seconds', '?')}s",
                (s.get("tts_text", "") or "")[:40]
                + ("..." if len(s.get("tts_text", "") or "") > 40 else ""),
            )
        console.print(table)

    # Assets summary
    images = data.get("images", [])
    videos = data.get("videos", [])
    audios = data.get("audios", [])
    console.print(
        f"\n[bold]Assets:[/bold]  Images: {len(images)}  |  "
        f"Videos: {len(videos)}  |  Audios: {len(audios)}"
    )


async def cmd_projects_create(args: argparse.Namespace) -> None:
    """Create a project via WebSocket pipeline.

    Connects to ws://host/ws/pipeline/{id}, sends config, and streams events.
    Falls back to a note if websockets aren't available.
    """
    try:
        import websockets  # type: ignore[import-untyped]
    except ImportError:
        # websockets not installed — give curl instructions
        console.print(
            Panel(
                "[yellow]The 'websockets' package is not installed.[/yellow]\n\n"
                "Project creation uses a WebSocket endpoint. Install it:\n"
                "  [bold]pip install websockets[/bold]\n\n"
                "Or use curl/wscat directly:\n"
                "  wscat -c ws://localhost:8000/ws/pipeline/new \\\n"
                '    -x \'{"story": "Your story...", "character": "skeleton", "mode": "plan"}\'',
                title="WebSocket Required",
                border_style="yellow",
            )
        )
        return

    import uuid

    ws_url = args.api_url.replace("http://", "ws://").replace("https://", "wss://")
    project_id = str(uuid.uuid4())
    url = f"{ws_url}/ws/pipeline/{project_id}"

    config_payload = {
        "story": args.story,
        "mode": args.mode or "plan",
        "character": args.character or "skeleton",
    }
    if args.platform:
        config_payload["platform"] = args.platform

    if not args.json:
        console.print(f"[bold]Connecting to pipeline...[/bold] {url}")

    try:
        async with websockets.connect(url) as ws:  # type: ignore[attr-defined]
            await ws.send(json.dumps(config_payload))

            if not args.json:
                console.print(
                    "[green]Connected.[/green] Streaming pipeline events...\n"
                )

            events: list[dict[str, Any]] = []
            async for message in ws:
                try:
                    evt = json.loads(message)
                except json.JSONDecodeError:
                    continue

                events.append(evt)
                etype = evt.get("event_type", "")

                if args.json:
                    continue

                if etype == "agent_start":
                    console.print(
                        f"  [cyan]▶[/cyan] {evt.get('agent_name', 'Agent')} starting..."
                    )
                elif etype == "agent_done":
                    console.print(
                        f"  [green]✓[/green] {evt.get('agent_name', 'Agent')} done"
                    )
                elif etype == "image_generated":
                    console.print(
                        f"  [green]🖼[/green] Image scene {evt.get('image_scene_number', '?')}"
                    )
                elif etype == "error":
                    console.print(f"  [red]✗[/red] Error: {evt.get('message', '')}")
                elif etype == "result":
                    plan = evt.get("data", {})
                    console.print(
                        Panel(
                            f"[green bold]Pipeline complete![/green bold]\n\n"
                            f"Title: {plan.get('title', '-')}\n"
                            f"Scenes: {len(plan.get('scenes', []))}\n"
                            f"Project ID: [cyan]{project_id}[/cyan]",
                            title="[bold green]Done[/bold green]",
                            border_style="green",
                        )
                    )

            if args.json:
                _print_json({"project_id": project_id, "events": events})

    except Exception as exc:
        console.print(f"[red]WebSocket error:[/red] {exc}")
        sys.exit(1)


async def cmd_projects_delete(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "DELETE", f"/api/projects/{args.project_id}")
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    console.print(f"[green]✓[/green] Deleted project [cyan]{args.project_id}[/cyan]")


async def cmd_projects_assets(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "GET", f"/api/projects/{args.project_id}/assets")
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    assets = data.get("assets", {})
    summary = data.get("summary", {})

    console.print(
        Panel(
            f"Images: {summary.get('generated_images', 0)}/{summary.get('total_images', 0)}  |  "
            f"Videos: {summary.get('generated_videos', 0)}/{summary.get('total_videos', 0)}  |  "
            f"Audios: {summary.get('generated_audios', 0)}/{summary.get('total_audios', 0)}",
            title=f"[bold cyan]Assets – {args.project_id[:8]}...[/bold cyan]",
            border_style="cyan",
        )
    )

    for asset_type in ("images", "videos", "audios"):
        items = assets.get(asset_type, [])
        if not items:
            continue
        table = Table(title=asset_type.capitalize(), box=box.SIMPLE)
        table.add_column("Scene", justify="right")
        table.add_column("Status")
        table.add_column("File", max_width=60)
        for item in items:
            table.add_row(
                str(item.get("scene_number", "")),
                item.get("status", "-"),
                item.get("file_path", "-").split("/")[-1],
            )
        console.print(table)


async def cmd_projects_status(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(
            c, "GET", f"/api/projects/{args.project_id}/generation-status"
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    progress_info = data.get("progress", {})
    console.print(
        Panel(
            f"Images: {progress_info.get('images', {}).get('done', 0)}/{progress_info.get('images', {}).get('total', 0)}  |  "
            f"Videos: {progress_info.get('videos', {}).get('done', 0)}/{progress_info.get('videos', {}).get('total', 0)}  |  "
            f"Audios: {progress_info.get('audios', {}).get('done', 0)}/{progress_info.get('audios', {}).get('total', 0)}",
            title="[bold cyan]Generation Status[/bold cyan]",
            border_style="cyan",
        )
    )

    scenes_data = data.get("scenes", [])
    if scenes_data:
        table = Table(box=box.SIMPLE)
        table.add_column("#", justify="right")
        table.add_column("Image")
        table.add_column("Video")
        table.add_column("Audio")
        for s in scenes_data:

            def _icon(status: str) -> Text:
                if status in ("generated", "approved"):
                    return Text("✓", style="green")
                elif status == "pending":
                    return Text("○", style="dim")
                elif status == "error":
                    return Text("✗", style="red")
                return Text(status or "–", style="dim")

            table.add_row(
                str(s.get("scene_number", "")),
                _icon(s.get("image", "")),
                _icon(s.get("video", "")),
                _icon(s.get("audio", "")),
            )
        console.print(table)


async def cmd_projects_export_status(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(
            c, "GET", f"/api/projects/{args.project_id}/export-status"
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    exported = data.get("exported", False)
    if exported:
        size = data.get("file_size_bytes", 0)
        size_mb = size / (1024 * 1024) if size else 0
        console.print(
            Panel(
                f"[green bold]Export available[/green bold]\n"
                f"Path: {data.get('file_path', '-')}\n"
                f"Size: {size_mb:.1f} MB",
                border_style="green",
            )
        )
    else:
        console.print(
            "[dim]No export file found. Run: cli.py export video PROJECT_ID[/dim]"
        )


# ---------------------------------------------------------------------------
# generate
# ---------------------------------------------------------------------------


async def cmd_generate_images(args: argparse.Namespace) -> None:
    async with _client(args.api_url, timeout=600.0) as c:
        await _stream_sse(
            c,
            "POST",
            f"/api/projects/{args.project_id}/generate-images",
            label="Images",
            json_mode=args.json,
        )


async def cmd_generate_videos(args: argparse.Namespace) -> None:
    timeout = 1200.0  # videos take longer
    parallel = getattr(args, "parallel", 1) or 1
    path = f"/api/projects/{args.project_id}/generate-videos"
    if parallel > 1:
        path += f"?parallel={parallel}"
        console.print(f"[bold]Parallel mode:[/bold] {parallel} concurrent workers")
    async with _client(args.api_url, timeout=timeout) as c:
        await _stream_sse(
            c,
            "POST",
            path,
            label="Videos",
            json_mode=args.json,
        )


async def cmd_generate_audios(args: argparse.Namespace) -> None:
    async with _client(args.api_url, timeout=600.0) as c:
        await _stream_sse(
            c,
            "POST",
            f"/api/projects/{args.project_id}/generate-audios",
            label="Audios",
            json_mode=args.json,
        )


async def cmd_generate_subtitles(args: argparse.Namespace) -> None:
    async with _client(args.api_url, timeout=120.0) as c:
        resp = await _request(
            c, "POST", f"/api/projects/{args.project_id}/generate-subtitles"
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    subtitles = data.get("subtitles", [])
    count = data.get("count", len(subtitles))
    console.print(
        f"[green]✓[/green] Generated [bold]{count}[/bold] word-level subtitle entries"
    )

    if subtitles:
        table = Table(title="Subtitles (first 20)", box=box.SIMPLE)
        table.add_column("Scene", justify="right")
        table.add_column("Word")
        table.add_column("Start", justify="right")
        table.add_column("End", justify="right")
        for s in subtitles[:20]:
            table.add_row(
                str(s.get("scene", "")),
                s.get("word", ""),
                f"{s.get('start', 0):.3f}s",
                f"{s.get('end', 0):.3f}s",
            )
        console.print(table)
        if count > 20:
            console.print(
                f"[dim]... and {count - 20} more. Use --json for full output.[/dim]"
            )


# ---------------------------------------------------------------------------
# export
# ---------------------------------------------------------------------------


async def cmd_export_video(args: argparse.Namespace) -> None:
    # The export endpoint expects editor data; send minimal payload
    # to trigger a simple sequential export.
    console.print("[bold]Exporting final video...[/bold]")

    # First fetch project to build a minimal editor payload
    async with _client(args.api_url, timeout=600.0) as c:
        proj_resp = await _request(c, "GET", f"/api/projects/{args.project_id}")
        proj = proj_resp.json()

        result = proj.get("result", {})
        scenes = result.get("scenes", []) if isinstance(result, dict) else []
        images = proj.get("images", [])
        videos = proj.get("videos", [])
        audios = proj.get("audios", [])

        # Build video track clips from videos (prefer) or images
        video_clips = []
        audio_clips = []
        t = 0.0
        for s in scenes:
            sn = s.get("scene_number", 0)
            dur = s.get("duration_seconds", 3.0)

            # Video source
            vid = next(
                (
                    v
                    for v in videos
                    if v.get("scene_number") == sn and v.get("status") == "generated"
                ),
                None,
            )
            img = next(
                (
                    i
                    for i in images
                    if i.get("scene_number") == sn
                    and i.get("status") in ("generated", "approved")
                ),
                None,
            )

            if vid:
                fp = vid.get("file_path", "")
                rel = fp.replace(str(Path.cwd()), "").lstrip("/")
                video_clips.append(
                    {
                        "id": f"v{sn}",
                        "videoUrl": f"/{rel}" if not rel.startswith("/") else rel,
                        "startTime": t,
                        "duration": dur,
                    }
                )
            elif img:
                fp = img.get("file_path", "")
                rel = fp.replace(str(Path.cwd()), "").lstrip("/")
                video_clips.append(
                    {
                        "id": f"i{sn}",
                        "imageUrl": f"/{rel}" if not rel.startswith("/") else rel,
                        "startTime": t,
                        "duration": dur,
                    }
                )

            # Audio source
            aud = next(
                (
                    a
                    for a in audios
                    if a.get("scene_number") == sn and a.get("status") == "generated"
                ),
                None,
            )
            if aud:
                fp = aud.get("file_path", "")
                rel = fp.replace(str(Path.cwd()), "").lstrip("/")
                audio_clips.append(
                    {
                        "id": f"a{sn}",
                        "src": f"/{rel}" if not rel.startswith("/") else rel,
                        "startTime": t,
                        "duration": dur,
                    }
                )

            t += dur

        editor_payload = {
            "tracks": [
                {"id": "video", "clips": video_clips},
                {"id": "audio", "clips": audio_clips},
                {"id": "subtitle", "clips": []},
            ],
            "duration": t,
            "transitions": [],
            "effects": {
                "videoVolume": getattr(args, 'video_volume', 0.5),
                "subtitleStyle": {
                    "preset": getattr(args, 'subtitle_style', 'tiktok_karaoke'),
                    "position": getattr(args, 'subtitle_position', 'bottom'),
                    "karaoke": getattr(args, 'subtitle_karaoke', True),
                },
            },
        }

        resp = await _request(
            c,
            "POST",
            f"/api/projects/{args.project_id}/export-video",
            json_body=editor_payload,
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    if data.get("status") == "completed":
        size = data.get("file_size", 0)
        size_mb = size / (1024 * 1024) if size else 0
        console.print(
            Panel(
                f"[green bold]Export complete![/green bold]\n\n"
                f"Download: {data.get('download_url', '-')}\n"
                f"Size: {size_mb:.1f} MB",
                title="[bold green]Export[/bold green]",
                border_style="green",
            )
        )
    else:
        console.print(f"[yellow]Export status:[/yellow] {data}")


async def cmd_export_srt(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(
            c, "GET", f"/api/projects/{args.project_id}/subtitles.srt"
        )

    srt_text = resp.text

    if args.output:
        Path(args.output).write_text(srt_text, encoding="utf-8")
        console.print(f"[green]✓[/green] SRT saved to [bold]{args.output}[/bold]")
    elif args.json:
        _print_json({"srt": srt_text})
    else:
        console.print(
            Panel(srt_text, title="[bold]SRT Subtitles[/bold]", border_style="cyan")
        )


async def cmd_export_variants(args: argparse.Namespace) -> None:
    """Export multiple video variants with different editing styles."""
    console.print("[bold]Exporting video variants...[/bold]")

    async with _client(args.api_url, timeout=600.0) as c:
        resp = await _request(
            c,
            "POST",
            f"/api/projects/{args.project_id}/export-variants",
            json_body={
                "seed": getattr(args, "seed", 42),
                "variants": getattr(args, "variants", None),
            },
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    variants = data.get("variants", {})
    for vname, vdata in variants.items():
        status = vdata.get("status", "unknown")
        if status == "completed":
            size = vdata.get("file_size", 0)
            size_mb = size / (1024 * 1024) if size else 0
            console.print(f"  [green]\u2713[/green] {vname}: {vdata.get('download_url', '-')} ({size_mb:.1f} MB)")
        else:
            console.print(f"  [red]\u2717[/red] {vname}: {vdata.get('error', 'failed')[:100]}")


# ---------------------------------------------------------------------------
# cookies (Grok accounts)
# ---------------------------------------------------------------------------


async def cmd_cookies_list(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "GET", "/api/settings/grok-cookies")
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    accounts = data.get("accounts", [])
    if not accounts:
        console.print("[dim]No Grok accounts configured.[/dim]")
        return

    console.print(
        f"[bold]Total:[/bold] {data.get('total', 0)}  |  "
        f"[bold]Active:[/bold] {data.get('active', 0)}\n"
    )

    table = Table(title="Grok Accounts", box=box.ROUNDED)
    table.add_column("ID", style="cyan", justify="right")
    table.add_column("Label", style="bold")
    table.add_column("User ID", max_width=20)
    table.add_column("Active", justify="center")
    table.add_column("Usage", justify="right")
    table.add_column("Daily", justify="right")
    table.add_column("Last Used")

    for a in accounts:
        active = "[green]●[/green]" if a.get("is_active") else "[red]○[/red]"
        table.add_row(
            str(a.get("id", "")),
            a.get("label", "-"),
            (a.get("user_id", "") or "")[:20],
            active,
            str(a.get("usage_count", 0)),
            str(a.get("daily_usage_count", 0)),
            str(a.get("last_used_at", "-"))[:19],
        )

    console.print(table)


async def cmd_cookies_add(args: argparse.Namespace) -> None:
    body = [
        {"name": "sso", "value": args.sso},
        {"name": "sso-rw", "value": args.sso_rw},
        {"name": "x-userid", "value": args.user_id},
    ]

    async with _client(args.api_url) as c:
        resp = await _request(c, "POST", "/api/settings/grok-cookies", json_body=body)
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    console.print(
        f"[green]✓[/green] Added Grok account [bold]{data.get('label', '')}[/bold] "
        f"(ID: {data.get('account_id', '-')})"
    )


async def cmd_cookies_delete(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(
            c, "DELETE", f"/api/settings/grok-cookies/{args.account_id}"
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    console.print(
        f"[green]✓[/green] Deleted Grok account [cyan]{args.account_id}[/cyan]"
    )


async def cmd_cookies_validate(args: argparse.Namespace) -> None:
    body = [
        {"name": "sso", "value": args.sso},
        {"name": "sso-rw", "value": args.sso_rw},
        {"name": "x-userid", "value": args.user_id},
    ]

    async with _client(args.api_url) as c:
        resp = await _request(
            c, "POST", "/api/settings/grok-cookies/validate", json_body=body
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    valid = data.get("valid", False)
    if valid:
        console.print("[green bold]✓ Cookies are valid![/green bold]")
    else:
        console.print(
            f"[red bold]✗ Cookies invalid.[/red bold] {data.get('message', '')}"
        )


# ---------------------------------------------------------------------------
# settings
# ---------------------------------------------------------------------------


async def cmd_settings_show(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "GET", "/api/settings")
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    # LLM section
    llm = data.get("llm", {})
    console.print(
        Panel(
            f"Provider: [bold]{llm.get('provider', '-')}[/bold]\n"
            f"Model: [bold]{llm.get('model', '-')}[/bold]\n"
            f"OpenAI key: {llm.get('openai_key', '-')}\n"
            f"Anthropic key: {llm.get('anthropic_key', '-')}",
            title="[bold cyan]LLM[/bold cyan]",
            border_style="cyan",
        )
    )

    # Providers
    table = Table(title="Active Providers", box=box.SIMPLE)
    table.add_column("Type", style="bold")
    table.add_column("Provider")
    table.add_row("Image", data.get("image_provider", "-"))
    table.add_row("Video", data.get("video_provider", "-"))
    table.add_row("TTS", data.get("tts_provider", "-"))
    console.print(table)

    # Provider details
    for section_name in ("google_flow", "grok", "leonardo", "pollinations"):
        section = data.get(section_name, {})
        if section:
            lines = [f"  {k}: {v}" for k, v in section.items()]
            console.print(f"\n[bold]{section_name}[/bold]")
            for line in lines:
                console.print(line)


async def cmd_settings_set(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(
            c, "POST", "/api/settings", json_body={args.key: args.value}
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    console.print(f"[green]✓[/green] Updated: {data.get('keys', [])}")


async def cmd_settings_provider(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(
            c,
            "PATCH",
            "/api/settings/provider",
            json_body={
                "type": args.type,
                "provider": args.provider,
            },
        )
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    console.print(
        f"[green]✓[/green] Set [bold]{args.type}[/bold] provider to "
        f"[bold]{args.provider}[/bold]"
    )


async def cmd_settings_flow_cookies(args: argparse.Namespace) -> None:
    body = [
        {"name": "__Secure-next-auth.session-token", "value": args.session},
        {"name": "__Host-next-auth.csrf-token", "value": args.csrf},
    ]

    async with _client(args.api_url) as c:
        resp = await _request(c, "POST", "/api/settings/cookies", json_body=body)
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    console.print(
        f"[green]✓[/green] Flow cookies updated. "
        f"Session: {data.get('session_token_preview', '-')}"
    )


# ---------------------------------------------------------------------------
# providers
# ---------------------------------------------------------------------------


async def cmd_providers_health(args: argparse.Namespace) -> None:
    async with _client(args.api_url) as c:
        resp = await _request(c, "GET", "/api/providers/health")
        data = resp.json()

    if args.json:
        _print_json(data)
        return

    providers = data.get("providers", data)

    table = Table(title="Provider Health", box=box.ROUNDED)
    table.add_column("Type", style="bold")
    table.add_column("Provider")
    table.add_column("Status", justify="center")
    table.add_column("Details")

    for ptype, info in providers.items():
        if isinstance(info, dict):
            status = info.get("status", "unknown")
            style = (
                "green"
                if status == "healthy"
                else ("red" if status == "error" else "yellow")
            )
            table.add_row(
                ptype,
                info.get("name", "-"),
                Text(status, style=style),
                info.get("details", "-"),
            )
        else:
            table.add_row(ptype, str(info), Text("?", style="dim"), "")

    console.print(table)


# ═══════════════════════════════════════════════════════════════════════════
# Argument parser
# ═══════════════════════════════════════════════════════════════════════════


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="cli.py",
        description="Video Production Pipeline – HTTP API CLI",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "Examples:\n"
            "  python cli.py projects list\n"
            "  python cli.py projects show PROJECT_ID\n"
            "  python cli.py generate images PROJECT_ID\n"
            "  python cli.py export video PROJECT_ID\n"
            "  python cli.py cookies list\n"
            "  python cli.py settings show\n"
            "  python cli.py providers health\n"
        ),
    )
    parser.add_argument(
        "--api-url",
        default=DEFAULT_API_URL,
        help=f"API base URL (default: {DEFAULT_API_URL})",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Output raw JSON instead of formatted tables",
    )

    subparsers = parser.add_subparsers(dest="command", help="Command group")

    # ── projects ───────────────────────────────────────────────────────────
    projects_parser = subparsers.add_parser("projects", help="Project management")
    projects_sub = projects_parser.add_subparsers(dest="subcommand")

    projects_sub.add_parser("list", help="List all projects")

    show_p = projects_sub.add_parser("show", help="Show project details")
    show_p.add_argument("project_id", help="Project UUID")

    create_p = projects_sub.add_parser(
        "create", help="Create a new project via WebSocket pipeline"
    )
    create_p.add_argument(
        "--story", required=True, help="Story text or path to .txt file"
    )
    create_p.add_argument(
        "--platform",
        default="tiktok",
        choices=["tiktok", "instagram_reels", "youtube_shorts", "youtube"],
        help="Target platform (default: tiktok)",
    )
    create_p.add_argument(
        "--character", default="skeleton", help="Character template name"
    )
    create_p.add_argument(
        "--mode",
        default="plan",
        choices=["plan", "plan_images", "generate"],
        help="Pipeline mode (default: plan)",
    )

    delete_p = projects_sub.add_parser("delete", help="Delete a project")
    delete_p.add_argument("project_id", help="Project UUID")

    assets_p = projects_sub.add_parser("assets", help="Show detailed asset metadata")
    assets_p.add_argument("project_id", help="Project UUID")

    status_p = projects_sub.add_parser(
        "status", help="Show generation status per scene"
    )
    status_p.add_argument("project_id", help="Project UUID")

    export_st_p = projects_sub.add_parser(
        "export-status", help="Check export file status"
    )
    export_st_p.add_argument("project_id", help="Project UUID")

    # ── generate ───────────────────────────────────────────────────────────
    gen_parser = subparsers.add_parser(
        "generate", help="Generate assets (images, videos, audios, subtitles)"
    )
    gen_sub = gen_parser.add_subparsers(dest="subcommand")

    gen_img = gen_sub.add_parser("images", help="Generate images via SSE stream")
    gen_img.add_argument("project_id", help="Project UUID")

    gen_vid = gen_sub.add_parser("videos", help="Generate videos via SSE stream")
    gen_vid.add_argument("project_id", help="Project UUID")
    gen_vid.add_argument(
        "--parallel", type=int, default=1, help="Parallel generation hint (default: 1)"
    )

    gen_aud = gen_sub.add_parser("audios", help="Generate TTS audio via SSE stream")
    gen_aud.add_argument("project_id", help="Project UUID")

    gen_sub_p = gen_sub.add_parser("subtitles", help="Generate word-level subtitles")
    gen_sub_p.add_argument("project_id", help="Project UUID")

    # ── export ─────────────────────────────────────────────────────────────
    export_parser = subparsers.add_parser(
        "export", help="Export final video or subtitles"
    )
    export_sub = export_parser.add_subparsers(dest="subcommand")

    exp_vid = export_sub.add_parser("video", help="Export final composed video")
    exp_vid.add_argument("project_id", help="Project UUID")
    exp_vid.add_argument(
        "--video-volume", type=float, default=0.5,
        help="Volume of video clip audio (0.0=mute, 1.0=full, default 0.5)"
    )
    exp_vid.add_argument(
        "--subtitle-style", default="tiktok_karaoke",
        help="Subtitle style preset (tiktok_karaoke, minimal, bold_center, neon, classic_bottom)"
    )
    exp_vid.add_argument(
        "--subtitle-position", default="bottom",
        help="Subtitle position (bottom, center, top)"
    )
    exp_vid.add_argument(
        "--subtitle-karaoke", action="store_true", default=True,
        help="Enable karaoke word-by-word highlighting (default: enabled)"
    )

    exp_srt = export_sub.add_parser("srt", help="Download SRT subtitles")
    exp_srt.add_argument("project_id", help="Project UUID")
    exp_srt.add_argument("--output", "-o", help="Save SRT to file path")


    exp_var = export_sub.add_parser("variants", help="Export 4 video editing variants (cinematic, dynamic, minimal, energetic)")
    exp_var.add_argument("project_id", help="Project UUID")
    exp_var.add_argument("--seed", type=int, default=42, help="Random seed for variant generation (default: 42)")
    exp_var.add_argument("--variants", nargs="+", default=None, help="Specific variants to generate (default: all 4)")

    # ── cookies ────────────────────────────────────────────────────────────
    cookies_parser = subparsers.add_parser(
        "cookies", help="Grok cookie/account management"
    )
    cookies_sub = cookies_parser.add_subparsers(dest="subcommand")

    cookies_sub.add_parser("list", help="List all Grok accounts")

    cookies_add = cookies_sub.add_parser("add", help="Add a Grok account")
    cookies_add.add_argument("--label", default="", help="Account label")
    cookies_add.add_argument("--sso", required=True, help="SSO token")
    cookies_add.add_argument("--sso-rw", required=True, help="SSO-RW token")
    cookies_add.add_argument("--user-id", required=True, help="User ID (UUID)")

    cookies_del = cookies_sub.add_parser("delete", help="Delete a Grok account")
    cookies_del.add_argument("account_id", help="Account ID")

    cookies_val = cookies_sub.add_parser("validate", help="Validate Grok cookies")
    cookies_val.add_argument("--sso", required=True, help="SSO token")
    cookies_val.add_argument("--sso-rw", required=True, help="SSO-RW token")
    cookies_val.add_argument("--user-id", required=True, help="User ID (UUID)")

    # ── settings ───────────────────────────────────────────────────────────
    settings_parser = subparsers.add_parser("settings", help="View and update settings")
    settings_sub = settings_parser.add_subparsers(dest="subcommand")

    settings_sub.add_parser("show", help="Show all settings")

    settings_set = settings_sub.add_parser("set", help="Set a setting value")
    settings_set.add_argument(
        "key", help="Setting key (e.g. llm_model, elevenlabs_key)"
    )
    settings_set.add_argument("value", help="Setting value")

    settings_prov = settings_sub.add_parser(
        "provider", help="Set active provider for a type"
    )
    settings_prov.add_argument(
        "type", choices=["image", "video", "tts"], help="Provider type"
    )
    settings_prov.add_argument(
        "provider", help="Provider name (e.g. grok, pollinations, google_flow)"
    )

    settings_flow = settings_sub.add_parser(
        "flow-cookies", help="Set Google Flow cookies"
    )
    settings_flow.add_argument("--session", required=True, help="Session token")
    settings_flow.add_argument("--csrf", required=True, help="CSRF token")

    # ── providers ──────────────────────────────────────────────────────────
    prov_parser = subparsers.add_parser("providers", help="Provider health checks")
    prov_sub = prov_parser.add_subparsers(dest="subcommand")
    prov_sub.add_parser("health", help="Check all provider health status")

    return parser


# ═══════════════════════════════════════════════════════════════════════════
# Command dispatch
# ═══════════════════════════════════════════════════════════════════════════

# Map (command, subcommand) → handler function
_DISPATCH: dict[tuple[str, str], Any] = {
    ("projects", "list"): cmd_projects_list,
    ("projects", "show"): cmd_projects_show,
    ("projects", "create"): cmd_projects_create,
    ("projects", "delete"): cmd_projects_delete,
    ("projects", "assets"): cmd_projects_assets,
    ("projects", "status"): cmd_projects_status,
    ("projects", "export-status"): cmd_projects_export_status,
    ("generate", "images"): cmd_generate_images,
    ("generate", "videos"): cmd_generate_videos,
    ("generate", "audios"): cmd_generate_audios,
    ("generate", "subtitles"): cmd_generate_subtitles,
    ("export", "video"): cmd_export_video,
    ("export", "srt"): cmd_export_srt,
    ("export", "variants"): cmd_export_variants,
    ("cookies", "list"): cmd_cookies_list,
    ("cookies", "add"): cmd_cookies_add,
    ("cookies", "delete"): cmd_cookies_delete,
    ("cookies", "validate"): cmd_cookies_validate,
    ("settings", "show"): cmd_settings_show,
    ("settings", "set"): cmd_settings_set,
    ("settings", "provider"): cmd_settings_provider,
    ("settings", "flow-cookies"): cmd_settings_flow_cookies,
    ("providers", "health"): cmd_providers_health,
}


async def main() -> None:
    parser = build_parser()
    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(0)

    key = (args.command, args.subcommand)
    handler = _DISPATCH.get(key)

    if handler is None:
        # Show help for the command group
        # Re-parse to get the sub-parser's help
        parser.parse_args([args.command, "--help"])
        sys.exit(0)

    await handler(args)


if __name__ == "__main__":
    asyncio.run(main())
