# api/routes/assets.py
"""Asset management endpoints — Phase 6.

Ported from review_server.py:
  - _api_assets_list (line 8935)
  - _api_asset_import (line 8985)
  - _api_asset_turnarounds (line 9142)
  - _api_asset_delete (line 9183)
  - _api_asset_set_hero (line 9228)
  - _api_asset_generate_views (line 9255)
  - _handle_ai_rewrite (line 505)
  - _find_hero_image (line 9306) — ported as module-level function
"""

import json
import os
import re
import shutil
from pathlib import Path

from fastapi import APIRouter, Body, Depends
from fastapi.responses import JSONResponse

from ..deps import get_project, get_paths, _paths_for_project
from ..state import submit_task
from recoil.pipeline._lib.taxonomy import slugify_asset_id

router = APIRouter(tags=["assets"])


# ── Helper: find_hero_image (from review_server.py line 9306) ─────


def find_hero_image(char_id: str, project: str) -> Path | None:
    """Find the hero image for a character in the refs directory.

    Searches by canonical {slug}_hero.{ext} naming convention,
    then falls back to the first non-concept image file.
    """
    pp = _paths_for_project(project)
    slug = slugify_asset_id(char_id)
    subject_folder = pp["character_refs_dir"] / slug
    if not subject_folder.is_dir():
        return None

    _img_exts = (".png", ".jpg", ".jpeg", ".webp")

    # Check canonical hero naming: {slug}_hero.{ext}
    for ext in _img_exts:
        candidate = subject_folder / f"{slug}_hero{ext}"
        if candidate.is_file():
            return candidate

    # First non-concept image
    for f in sorted(subject_folder.iterdir()):
        if (
            f.is_file()
            and f.suffix.lower() in _img_exts
            and "concept" not in f.stem.lower()
        ):
            return f

    return None


# ── Endpoints ─────────────────────────────────────────────────────


@router.get("/api/assets")
def assets_list(
    project: str = Depends(get_project),
    paths: dict = Depends(get_paths),
):
    """List all assets (characters, locations, props) with their refs."""
    bible_path = paths["state_dir"] / "client_bible.json"
    if not bible_path.exists():
        bible_path = paths.get("bible_path", paths["state_dir"] / "global_bible.json")

    result = {"characters": {}, "locations": {}, "props": {}}

    if bible_path.exists():
        try:
            bible = json.loads(bible_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            bible = {}
    else:
        bible = {}

    refs_dir = paths["output_dir"] / "refs"

    for asset_type in ("characters", "locations", "props"):
        bible_section = bible.get(asset_type, {})
        type_refs_dir = refs_dir / asset_type
        for asset_id, asset_data in bible_section.items():
            entry = dict(asset_data)
            entry["id"] = asset_id
            entry["refs"] = []
            slug = slugify_asset_id(asset_id)
            subject_folder = type_refs_dir / slug
            if subject_folder.is_dir():
                # Detect hero via {slug}_hero.{ext} filename pattern
                hero_prefix = f"{slug}_hero"
                imgs = sorted(
                    [
                        img
                        for img in subject_folder.iterdir()
                        if img.suffix.lower() in (".png", ".jpg", ".jpeg", ".webp")
                    ],
                    key=lambda p: (0 if p.stem == hero_prefix else 1, p.name),
                )
                for img in imgs:
                    is_hero = img.stem == hero_prefix
                    entry["refs"].append(
                        {
                            "filename": img.name,
                            "path": f"/refs/{asset_type}/{slug}/{img.name}",
                            "hero": is_hero,
                        }
                    )
            result[asset_type][asset_id] = entry

    return JSONResponse(result)


@router.post("/api/asset/import")
def asset_import(
    body: dict = Body(default={}),
    project: str = Depends(get_project),
    paths: dict = Depends(get_paths),
):
    """Import a new asset ref image (base64-encoded)."""
    file_data = body.get("file_data")
    file_name = body.get("file_name", "import.png")
    asset_type = body.get("type")
    asset_name = body.get("name")
    asset_id = body.get("asset_id")
    description = body.get("description", "")

    if not file_data:
        return JSONResponse({"error": "Missing file_data (base64)"}, status_code=400)
    if not asset_type or asset_type not in ("character", "location", "prop"):
        return JSONResponse(
            {"error": "type must be character, location, or prop"}, status_code=400
        )
    if not asset_name:
        return JSONResponse({"error": "Missing name"}, status_code=400)

    if not asset_id:
        asset_id = asset_name.lower().replace(" ", "_")

    # Sanitize asset_id to prevent path traversal
    asset_id = asset_id.replace("/", "").replace("\\", "").replace("..", "").strip(".")
    if not asset_id:
        return JSONResponse(
            {"error": "Invalid asset name (sanitized to empty)"}, status_code=400
        )

    import base64 as _b64

    try:
        raw = _b64.b64decode(file_data)
    except Exception as exc:
        return JSONResponse(
            {"error": f"Failed to decode file_data: {exc}"}, status_code=400
        )

    type_plural = asset_type + "s"
    slug = slugify_asset_id(asset_id)
    import_target = paths["output_dir"] / "refs" / type_plural / slug
    import_target.mkdir(parents=True, exist_ok=True)

    _IMG_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
    existing_images = [
        f
        for f in import_target.iterdir()
        if f.is_file()
        and f.suffix.lower() in _IMG_EXTS
        and "concept" not in f.stem.lower()
    ]
    # Hero if no non-concept images exist yet, or no file named *_hero* exists
    has_hero = any("_hero" in f.stem.lower() for f in existing_images)
    is_hero = not has_hero
    idx = len(existing_images) + 1
    suffix = Path(file_name).suffix or ".png"

    try:
        from PIL import Image
        import io

        img = Image.open(io.BytesIO(raw)).convert("RGBA")
        processed_bytes = raw
        original_b64 = _b64.b64encode(raw).decode()

        if asset_type in ("character", "prop"):
            try:
                from rembg import remove

                nobg = remove(raw)
                fg = Image.open(io.BytesIO(nobg)).convert("RGBA")

                if asset_type == "character":
                    target_size = (1024, 1024)
                else:
                    target_size = (512, 512)

                canvas = Image.new("RGBA", target_size, (255, 255, 255, 255))
                fg.thumbnail(target_size, Image.LANCZOS)
                offset = (
                    (target_size[0] - fg.width) // 2,
                    (target_size[1] - fg.height) // 2,
                )
                canvas.paste(fg, offset, fg)
                canvas = canvas.convert("RGB")

                buf = io.BytesIO()
                canvas.save(buf, format="PNG")
                processed_bytes = buf.getvalue()
            except ImportError:
                pass
        elif asset_type == "location":
            target_size = (1920, 1080)
            img_rgb = img.convert("RGB")
            img_rgb.thumbnail(target_size, Image.LANCZOS)
            buf = io.BytesIO()
            img_rgb.save(buf, format="PNG")
            processed_bytes = buf.getvalue()

        tag = "hero" if is_hero else f"ref_{idx:02d}"
        dest_name = f"{slug}_{tag}{suffix}"
        dest = import_target / dest_name
        dest.write_bytes(processed_bytes)

        processed_b64 = _b64.b64encode(processed_bytes).decode()

    except ImportError:
        tag = "hero" if is_hero else f"ref_{idx:02d}"
        dest_name = f"{slug}_{tag}{suffix}"
        dest = import_target / dest_name
        dest.write_bytes(raw)
        original_b64 = _b64.b64encode(raw).decode()
        processed_b64 = original_b64

    # Use same bible resolution as assets_list: client_bible first, then global_bible
    bible_path = paths["state_dir"] / "client_bible.json"
    if not bible_path.exists():
        bible_path = paths.get("bible_path", paths["state_dir"] / "global_bible.json")
    if bible_path.exists():
        try:
            bible = json.loads(bible_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            bible = {"characters": {}, "locations": {}, "props": {}}
    else:
        bible = {"characters": {}, "locations": {}, "props": {}}

    if type_plural not in bible:
        bible[type_plural] = {}

    if asset_id not in bible[type_plural]:
        if asset_type == "character":
            bible[type_plural][asset_id] = {
                "display_name": asset_name.upper(),
                "visual_description": description,
                "wardrobe_description": "",
                "hair_makeup_description": "",
                "height_cm": 170,
                "distinguishing_marks": "",
                "identity_type": "human",
            }
        elif asset_type == "location":
            bible[type_plural][asset_id] = {
                "display_name": asset_name,
                "description": description,
                "atmosphere": "",
                "lighting_notes": [],
                "palette": [],
            }
        elif asset_type == "prop":
            bible[type_plural][asset_id] = {
                "display_name": asset_name,
                "description": description,
            }

    bible_path.parent.mkdir(parents=True, exist_ok=True)
    bible_path.write_text(
        json.dumps(bible, indent=2, ensure_ascii=False), encoding="utf-8"
    )

    return JSONResponse(
        {
            "status": "ok",
            "asset_id": asset_id,
            "asset_type": asset_type,
            "ref_path": f"/refs/{type_plural}/{slug}/{dest_name}",
            "is_hero": is_hero,
            "original_b64": original_b64,
            "processed_b64": processed_b64,
        }
    )


@router.post("/api/asset/turnarounds")
def asset_turnarounds(
    body: dict = Body(default={}),
    project: str = Depends(get_project),
    paths: dict = Depends(get_paths),
):
    """Generate turnaround angles for a character asset."""
    char_id = body.get("asset_id") or body.get("character_id")
    if not char_id:
        return JSONResponse({"error": "Missing asset_id"}, status_code=400)

    # Sanitize to prevent path traversal
    char_id = char_id.replace("/", "").replace("\\", "").replace("..", "").strip(".")
    if not char_id:
        return JSONResponse(
            {"error": "Invalid asset_id (sanitized to empty)"}, status_code=400
        )

    hero_image = find_hero_image(char_id, project)

    # Get description from bible as fallback
    description = None
    if not hero_image:
        bible_path = paths.get("bible_path")
        if bible_path and bible_path.exists():
            try:
                bible = json.loads(bible_path.read_text(encoding="utf-8"))
                char_entry = bible.get("characters", {}).get(char_id.upper(), {})
                description = char_entry.get("physical_description") or char_entry.get(
                    "description"
                )
            except Exception:
                pass

    try:
        from tools.prep_character_angles import prep_character

        result = prep_character(
            project=project,
            character=char_id,
            hero_image=hero_image,
            description=description,
        )
        return JSONResponse({"status": "ok", "result": result})
    except Exception as exc:
        import traceback

        traceback.print_exc()
        return JSONResponse(
            {"error": f"Turnaround generation failed: {exc}"}, status_code=500
        )


@router.post("/api/asset/delete")
def asset_delete(
    body: dict = Body(default={}),
    project: str = Depends(get_project),
    paths: dict = Depends(get_paths),
):
    """Delete an asset ref image or entire asset."""
    asset_type = body.get("type")
    asset_id = body.get("asset_id")
    ref_filename = body.get("ref_filename")

    if not asset_type or not asset_id:
        return JSONResponse({"error": "Missing type or asset_id"}, status_code=400)

    # Sanitize inputs to prevent path traversal
    asset_id = asset_id.replace("/", "").replace("\\", "").replace("..", "").strip(".")
    if not asset_id:
        return JSONResponse(
            {"error": "Invalid asset_id (sanitized to empty)"}, status_code=400
        )
    if ref_filename:
        ref_filename = Path(ref_filename).name

    type_plural = asset_type + "s" if not asset_type.endswith("s") else asset_type
    slug = slugify_asset_id(asset_id)
    refs_dir = paths["output_dir"] / "refs" / type_plural / slug

    if ref_filename:
        target = refs_dir / ref_filename
        if target.exists():
            target.unlink()
        return JSONResponse({"status": "ok", "deleted": ref_filename})
    else:
        if refs_dir.exists():
            shutil.rmtree(refs_dir)
        # Use same bible resolution as assets_list
        bible_path = paths["state_dir"] / "client_bible.json"
        if not bible_path.exists():
            bible_path = paths.get(
                "bible_path", paths["state_dir"] / "global_bible.json"
            )
        if bible_path.exists():
            try:
                bible = json.loads(bible_path.read_text(encoding="utf-8"))
                if type_plural in bible and asset_id in bible[type_plural]:
                    del bible[type_plural][asset_id]
                    bible_path.write_text(
                        json.dumps(bible, indent=2, ensure_ascii=False),
                        encoding="utf-8",
                    )
            except (json.JSONDecodeError, OSError):
                pass
        return JSONResponse({"status": "ok", "deleted": asset_id})


@router.post("/api/asset/set-hero")
def asset_set_hero(
    body: dict = Body(default={}),
    project: str = Depends(get_project),
    paths: dict = Depends(get_paths),
):
    """Set a specific ref as the hero for an asset.

    Filesystem promotion: copies the chosen file to {slug}_hero.{ext} in
    the legacy refs directory AND to ``assets/<kind>/<slug>/hero.<ext>``
    in the v2 canonical asset directory.
    Metadata update: records provenance in casting_state.json.
    Does NOT write _hero.json (eliminated).
    """
    import shutil
    import time as _time
    from recoil.pipeline._lib.taxonomy import slugify_asset_id

    asset_type = body.get("type")
    asset_id = body.get("asset_id")
    ref_filename = body.get("ref_filename")

    if not asset_type or not asset_id or not ref_filename:
        return JSONResponse(
            {"error": "Missing type, asset_id, or ref_filename"}, status_code=400
        )

    asset_id_clean = (
        asset_id.replace("/", "").replace("\\", "").replace("..", "").strip(".")
    )
    ref_filename = Path(ref_filename).name
    type_plural = asset_type + "s" if not asset_type.endswith("s") else asset_type
    slug = slugify_asset_id(asset_id_clean)
    refs_dir = paths["output_dir"] / "refs" / type_plural / slug

    source = refs_dir / ref_filename
    if not source.exists():
        return JSONResponse({"error": f"Ref {ref_filename} not found"}, status_code=404)

    # === FILESYSTEM PROMOTION ===
    # Copy the chosen file to {slug}_hero.{ext} — this IS the truth
    hero_filename = f"{slug}_hero{source.suffix.lower()}"
    hero_dest = refs_dir / hero_filename
    if source.resolve() != hero_dest.resolve():
        shutil.copy2(str(source), str(hero_dest))

    # === CANONICAL ASSET PROMOTION (v2 layout) ===
    # Map legacy plurals to v2 kinds and route through ProjectPaths.
    from recoil.core.paths import ProjectPaths as _ProjectPaths

    _LEGACY_TO_CLS = {"characters": "char", "locations": "loc", "props": "prop"}
    cls = _LEGACY_TO_CLS.get(type_plural, type_plural)
    pp_obj = _ProjectPaths.for_project(project)
    canonical_dir = pp_obj.asset_subject_dir(cls, slug)
    canonical_dir.mkdir(parents=True, exist_ok=True)
    # Remove any existing hero with a different extension to prevent masking
    for old_hero in canonical_dir.glob("hero.*"):
        old_hero.unlink()
    canonical_dest = canonical_dir / f"hero{source.suffix.lower()}"
    shutil.copy2(str(source), str(canonical_dest))

    # === METADATA UPDATE ===
    # Update casting_state.json with provenance and canonical hero_path
    state_path = pp_obj.casting_state_path
    if state_path.exists():
        try:
            state = json.loads(state_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, IOError):
            state = {"characters": {}, "locations": {}, "grid_sessions": {}}
    else:
        state = {"characters": {}, "locations": {}, "grid_sessions": {}}

    canonical_rel = f"assets/{cls}/{slug}/hero{source.suffix.lower()}"

    if asset_type in ("character", "characters"):
        chars = state.setdefault("characters", {})
        cs = chars.setdefault(asset_id_clean.upper(), {})
        cs["status"] = "hero_selected"
        cs["hero_source"] = "console_assets"
        cs["hero_selected_at"] = _time.time()
        cs["bible_synced"] = False
        cs["hero_path"] = canonical_rel
    elif asset_type in ("location", "locations"):
        locs = state.setdefault("locations", {})
        ls = locs.setdefault(slug, {})
        ls["hero_source"] = "console_assets"
        ls["hero_selected_at"] = _time.time()
        ls["hero_path"] = canonical_rel

    state_path.parent.mkdir(parents=True, exist_ok=True)
    state_path.write_text(json.dumps(state, indent=2), encoding="utf-8")

    return JSONResponse(
        {"status": "ok", "hero": hero_filename, "promoted_to": str(hero_dest)}
    )


@router.post("/api/asset/generate-views")
def asset_generate_views(
    body: dict = Body(default={}),
    project: str = Depends(get_project),
    paths: dict = Depends(get_paths),
):
    """Generate multi-view refs for any asset type (non-blocking via submit_task)."""
    asset_type = body.get("type")
    asset_id = body.get("asset_id")

    if not asset_type or not asset_id:
        return JSONResponse({"error": "Missing type or asset_id"}, status_code=400)

    asset_id = asset_id.replace("/", "").replace("\\", "").replace("..", "").strip(".")

    def _do_generate():
        if asset_type == "character":
            from tools.prep_character_angles import prep_character

            hero_image = find_hero_image(asset_id, project)
            description = None
            if not hero_image:
                pp = _paths_for_project(project)
                bible_path = pp.get("bible_path")
                if bible_path and bible_path.exists():
                    try:
                        bible = json.loads(bible_path.read_text(encoding="utf-8"))
                        char_entry = bible.get("characters", {}).get(
                            asset_id.upper(), {}
                        )
                        description = char_entry.get(
                            "physical_description"
                        ) or char_entry.get("description")
                    except Exception:
                        pass
            return prep_character(
                project=project,
                character=asset_id,
                hero_image=hero_image,
                description=description,
            )
        elif asset_type == "location":
            from tools.prep_location_refs import generate_location_refs

            pp = _paths_for_project(project)
            bible = json.loads(pp["bible_path"].read_text(encoding="utf-8"))
            if asset_id in bible.get("locations", {}):
                bible["locations"] = {asset_id: bible["locations"][asset_id]}
            return generate_location_refs(bible, project=project)
        elif asset_type == "prop":
            from tools.prep_prop_refs import generate_prop_refs

            pp = _paths_for_project(project)
            bible = json.loads(pp["bible_path"].read_text(encoding="utf-8"))
            return generate_prop_refs(bible, project=project, prop_filter=asset_id)
        else:
            raise ValueError(f"Unknown asset type: {asset_type}")

    task_id = submit_task(
        entity_id=asset_id,
        action=f"{asset_type}_views",
        fn=_do_generate,
    )
    return JSONResponse({"task_id": task_id, "status": "submitted"}, status_code=202)


@router.post("/api/ai/rewrite")
def ai_rewrite(
    body: dict = Body(default={}),
):
    """Call Gemini 2.5 Pro to generate 2 rewrite proposals for selected text."""
    import urllib.request

    api_key = os.environ.get("GOOGLE_API_KEY")
    if not api_key:
        return JSONResponse({"error": "GOOGLE_API_KEY not set"}, status_code=500)

    selected_text = body.get("selected_text", "")
    line_type = body.get("line_type", "action")
    character = body.get("character")
    scene_context = body.get("scene_context", "")
    project_name = body.get("project", "")

    system_prompt = (
        f"You are an expert script doctor for the vertical microdrama '{project_name}'.\n"
        f"Provide EXACTLY TWO rewrite proposals for the selected {line_type} text.\n"
        f"Rules:\n"
        f"1. Preserve the line type. If the original is DIALOGUE, write DIALOGUE. If ACTION, write ACTION.\n"
        f"2. Proposal 1: subtle polish (tighter, more natural).\n"
        f"3. Proposal 2: stronger swing (more subtext, punchier, more stylized).\n"
        f"4. Keep word counts similar to the original. Vertical microdramas require fast pacing.\n"
        f'5. Output ONLY valid JSON: {{"proposals": ["rewrite 1", "rewrite 2"]}}\n'
    )

    user_content = json.dumps(
        {
            "scene_context": scene_context[:2000],
            "selected_text": selected_text,
            "line_type": line_type,
            "character": character,
        }
    )

    content_text = ""
    try:
        url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key={api_key}"
        req_data = json.dumps(
            {
                "systemInstruction": {"parts": [{"text": system_prompt}]},
                "contents": [{"role": "user", "parts": [{"text": user_content}]}],
                "generationConfig": {
                    "maxOutputTokens": 300,
                    "responseMimeType": "application/json",
                    "responseSchema": {
                        "type": "OBJECT",
                        "properties": {
                            "proposals": {"type": "ARRAY", "items": {"type": "STRING"}}
                        },
                        "required": ["proposals"],
                    },
                },
            }
        ).encode("utf-8")

        req = urllib.request.Request(
            url,
            headers={"content-type": "application/json"},
            data=req_data,
        )

        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read().decode("utf-8"))
            content_text = result["candidates"][0]["content"]["parts"][0]["text"]
            parsed = json.loads(content_text)
            return JSONResponse(parsed)
    except json.JSONDecodeError:
        try:
            m = re.search(r'\{[^}]*"proposals"[^}]*\}', content_text)
            if m:
                return JSONResponse(json.loads(m.group()))
            else:
                return JSONResponse(
                    {"error": "AI returned invalid JSON", "raw": content_text[:500]},
                    status_code=500,
                )
        except Exception:
            return JSONResponse({"error": "AI returned invalid JSON"}, status_code=500)
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


# RETIRED 2026-06-09: /api/enhance-prompt route — enrichment superseded by the
# prose_author strategy path; enhance_prompt() removed from jit_prompt.py.
