"""Assemble imported free-tier AI clips with local renderer fallbacks.

The module builds a deterministic assembly plan. It does not buy credits, call
paid APIs, or automate browser accounts. Human-generated/free-tier clips can be
placed under data/imported_ai_clips/<video_id>/<scene_id>.mp4 and then combined
with local mechanism/caption rendering in a later assembly step.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from autoshorts.script_drafts import _slug

VIDEO_EXTENSIONS = {".mp4", ".mov", ".webm", ".mkv"}
AI_VIDEO_MODES = {"cinematic_ai_video"}
LOCAL_LAYER_MODES = {"mechanism_animation", "kinetic_text_overlay", "screen_recording_simulation", "animated_card"}

CHATGPT_PRO_SORA_NOTE = {
    "provider": "chatgpt_pro_sora",
    "available_for_new_video_generation": False,
    "decision": "do_not_use_for_new_video_generation",
    "evidence": (
        "OpenAI Help Center states: Sora web and app experiences were discontinued on April 26, 2026; "
        "Sora API will be discontinued on September 24, 2026; current ChatGPT credits can only be used with Codex and ChatGPT for Excel."
    ),
    "source_urls": [
        "https://help.openai.com/en/articles/20001152-what-to-know-about-the-sora-discontinuation",
        "https://help.openai.com/en/articles/12642688-using-credits-for-flexible-usage-in-chatgpt-freegopluspro",
    ],
}


def discover_imported_ai_clips(video_id: str, imported_root: Path = Path("data/imported_ai_clips")) -> dict[str, Path]:
    """Return imported clip files keyed by scene_id for a video."""
    clip_dir = imported_root / video_id
    if not clip_dir.exists():
        return {}
    clips: dict[str, Path] = {}
    for path in sorted(clip_dir.iterdir()):
        if path.is_file() and path.suffix.lower() in VIDEO_EXTENSIONS and path.stem.startswith("scene_"):
            clips[path.stem] = path
    return clips


def _render_jobs(production_jobs: dict[str, Any]) -> list[dict[str, Any]]:
    return [job for job in production_jobs["jobs"] if job.get("provider") in {"local_renderer", "local_ffmpeg"}]


def _video_filename(video: dict[str, Any]) -> str:
    title_slug = _slug(video["title"])
    video_id = video["id"]
    if video_id.endswith(title_slug):
        return f"{video_id}.json"
    return f"{video_id}-{title_slug}.json"


def _scene_plan(job: dict[str, Any], video_id: str, imported_clips: dict[str, Path], imported_root: Path) -> dict[str, Any]:
    scene_id = job["scene_id"]
    asset_mode = job["asset_mode"]
    base = {
        "scene_id": scene_id,
        "asset_mode": asset_mode,
        "duration_seconds": job["duration_seconds"],
        "overlay_text": job.get("overlay_text", ""),
        "captions_added_locally": True,
    }

    if asset_mode in AI_VIDEO_MODES:
        imported_path = imported_clips.get(scene_id)
        if imported_path:
            return {
                **base,
                "source": "imported_ai_clip",
                "clip_path": str(imported_path),
                "role": "realistic_broll_under_local_captions",
                "validation_required": ["duration", "aspect_ratio", "watermark_check", "no_baked_caption_check"],
            }
        return {
            **base,
            "source": "local_renderer_fallback",
            "missing_expected_clip": str(imported_root / video_id / f"{scene_id}.mp4"),
            "role": "placeholder_until_free_tier_clip_is_imported",
        }

    if asset_mode in LOCAL_LAYER_MODES:
        return {
            **base,
            "source": "local_renderer",
            "role": "mechanism_or_caption_layer",
            "execution_mode": job.get("execution_mode", "local_motion"),
        }

    return {
        **base,
        "source": "local_renderer_fallback",
        "role": "unknown_mode_local_safe_default",
    }


def build_imported_clip_assembly_plan(
    production_jobs: dict[str, Any],
    imported_root: Path = Path("data/imported_ai_clips"),
) -> dict[str, Any]:
    """Build a plan that swaps imported realistic clips into local assembly."""
    video = production_jobs["video"]
    video_id = video["id"]
    imported_clips = discover_imported_ai_clips(video_id, imported_root)
    scenes = [_scene_plan(job, video_id, imported_clips, imported_root) for job in _render_jobs(production_jobs)]
    return {
        "schema_version": "imported_clip_assembly.v1",
        "video": video,
        "budget_policy": {
            "paid_video_credits_allowed": False,
            "paid_openai_video_generation_available": False,
            "manual_free_tier_import_only": True,
        },
        "provider_notes": [CHATGPT_PRO_SORA_NOTE],
        "imported_root": str(imported_root),
        "final_assembly": "local_ffmpeg",
        "scenes": scenes,
        "summary": {
            "imported_ai_clip_count": sum(1 for scene in scenes if scene["source"] == "imported_ai_clip"),
            "local_fallback_count": sum(1 for scene in scenes if scene["source"] == "local_renderer_fallback"),
            "local_renderer_count": sum(1 for scene in scenes if scene["source"] == "local_renderer"),
        },
    }


def write_imported_clip_assembly_plans(
    production_jobs_list: list[dict[str, Any]],
    output_dir: Path,
    imported_root: Path = Path("data/imported_ai_clips"),
) -> list[Path]:
    """Write assembly plans as JSON files."""
    output_dir.mkdir(parents=True, exist_ok=True)
    paths: list[Path] = []
    for production_jobs in production_jobs_list:
        plan = build_imported_clip_assembly_plan(production_jobs, imported_root=imported_root)
        path = output_dir / _video_filename(plan["video"])
        path.write_text(json.dumps(plan, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
        paths.append(path)
    return paths
