from __future__ import annotations

import json
import os
import shutil
import tempfile
from collections.abc import Callable
from pathlib import Path
from typing import Any

from packages.pet_package_schema import ALLOWED_ACTIONS
from services.ai.provider_metrics import provider_call_metrics_from_build_dir
from services.api.local_api import LocalPetApi
from services.api.local_store import LocalJsonStore
from services.pet_builder.photo_generation_worker import build_pet_package_from_photos
from services.storage.local_private_storage import LocalPrivateStorage


ROOT = Path(__file__).resolve().parents[2]
ZHOU_LIU_FIXTURE = ROOT / "tests" / "fixtures" / "public" / "周六-合成样例"
YI_NENG_JING_FIXTURE = ROOT / "tests" / "fixtures" / "public" / "一能静-合成样例"


PackageBuilder = Callable[..., Path]


def create_production_api(
    *,
    state_dir: Path,
    storage_dir: Path,
    output_dir: Path,
    build_mode: str | None = None,
    package_builder: PackageBuilder | None = None,
) -> LocalPetApi:
    state_dir.mkdir(parents=True, exist_ok=True)
    storage_dir.mkdir(parents=True, exist_ok=True)
    output_dir.mkdir(parents=True, exist_ok=True)
    media_storage = LocalPrivateStorage(storage_dir)
    runner_mode = (build_mode or os.environ.get("AI_PET_BUILD_RUNNER", "demo")).strip().lower()
    if runner_mode in {"demo", "fixture", "fixtures"}:
        build_runner = production_demo_build_runner
    elif runner_mode in {"external", "real", "model"}:
        selected_builder = package_builder or build_pet_package_from_photos

        def build_runner(*, build: dict[str, Any], pet: dict[str, Any], media: list[dict[str, Any]], output_root: Path) -> dict[str, Any]:
            return production_external_build_runner(
                build=build,
                pet=pet,
                media=media,
                output_root=output_root,
                media_storage=media_storage,
                package_builder=selected_builder,
            )

    else:
        raise ValueError("AI_PET_BUILD_RUNNER must be demo or external")
    return LocalPetApi(
        store=LocalJsonStore(state_dir / "local-api-store.json"),
        build_runner=build_runner,
        output_root=output_dir,
        media_storage=media_storage,
    )


def production_demo_build_runner(*, build: dict[str, Any], pet: dict[str, Any], media: list[dict[str, Any]], output_root: Path) -> dict[str, Any]:
    fixture = _select_public_fixture(pet=pet, media=media)
    build_dir = Path(output_root) / str(build["build_id"])
    package_dir = build_dir / "package"
    qa_dir = build_dir / "qa"
    if build_dir.exists():
        shutil.rmtree(build_dir)
    package_dir.mkdir(parents=True)
    qa_dir.mkdir(parents=True)

    for filename in ("pet.json", "spritesheet.webp", "spritesheet.png"):
        source = fixture / filename
        if source.exists():
            shutil.copy2(source, package_dir / filename)
    (qa_dir / "contact-sheet.html").write_text(
        _demo_contact_sheet(display_name=str(pet.get("pet_name") or "pet"), spritesheet="../package/spritesheet.webp"),
        encoding="utf-8",
    )
    preview_ref = "package/spritesheet.png" if (package_dir / "spritesheet.png").exists() else "package/spritesheet.webp"
    return {
        "build_dir": build_dir,
        "package_ref": "package/pet.json",
        "preview_ref": preview_ref,
        "actions": list(ALLOWED_ACTIONS),
        "checksum_valid": True,
        "runtime_compatible": True,
        "validation_ok": True,
    }


def production_external_build_runner(
    *,
    build: dict[str, Any],
    pet: dict[str, Any],
    media: list[dict[str, Any]],
    output_root: Path,
    media_storage: LocalPrivateStorage,
    package_builder: PackageBuilder = build_pet_package_from_photos,
) -> dict[str, Any]:
    if not media:
        raise ValueError("at least one uploaded pet photo is required")

    tmp_root = Path(output_root) / "_private_upload_inputs"
    tmp_root.mkdir(parents=True, exist_ok=True)
    with tempfile.TemporaryDirectory(prefix=f"{_safe_temp_prefix(str(build.get('build_id') or 'build'))}-", dir=tmp_root) as tmp:
        photo_paths = _materialize_private_media(media=media, media_storage=media_storage, tmp_dir=Path(tmp))
        build_dir = package_builder(
            pet_name=str(pet.get("pet_name") or "pet"),
            notes=_pet_generation_notes(pet),
            photo_paths=photo_paths,
            output_root=Path(output_root),
        )

    build_dir = Path(build_dir)
    if not (build_dir / "package" / "pet.json").exists():
        raise ValueError("generated package is missing package/pet.json")
    preview_ref = "qa/contact-sheet.png" if (build_dir / "qa" / "contact-sheet.png").exists() else "qa/contact-sheet.html"
    if not (build_dir / preview_ref).exists():
        raise ValueError("generated package is missing preview contact sheet")
    validation_ok = _generated_validation_ok(build_dir)
    return {
        "build_dir": build_dir,
        "package_ref": "package/pet.json",
        "preview_ref": preview_ref,
        "actions": list(ALLOWED_ACTIONS),
        "checksum_valid": True,
        "runtime_compatible": True,
        "validation_ok": validation_ok,
        "provider_call_metrics": provider_call_metrics_from_build_dir(build_dir),
    }


def _materialize_private_media(
    *,
    media: list[dict[str, Any]],
    media_storage: LocalPrivateStorage,
    tmp_dir: Path,
) -> list[Path]:
    photo_paths: list[Path] = []
    for index, item in enumerate(media):
        if item.get("deletion_status") not in {None, "active"}:
            raise ValueError("active uploaded media is required")
        storage_ref = str(item.get("storage_ref") or "")
        if not storage_ref:
            raise ValueError("uploaded media storage ref is missing")
        suffix = _media_suffix(item)
        photo_path = tmp_dir / f"input-{index}{suffix}"
        photo_path.write_bytes(media_storage.read(storage_ref))
        photo_paths.append(photo_path)
    return photo_paths


def _generated_validation_ok(build_dir: Path) -> bool:
    validation_path = build_dir / "validation.json"
    if not validation_path.exists():
        return False
    try:
        validation = json.loads(validation_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return False
    return validation.get("ok") is True


def _media_suffix(media: dict[str, Any]) -> str:
    suffix = Path(str(media.get("original_filename") or "")).suffix.lower()
    if suffix in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
        return suffix
    return {
        "image/png": ".png",
        "image/jpeg": ".jpg",
        "image/webp": ".webp",
        "image/gif": ".gif",
    }.get(str(media.get("mime_type") or "").lower(), ".img")


def _pet_generation_notes(pet: dict[str, Any]) -> str:
    parts = [
        str(pet.get("personality_notes") or "").strip(),
        str(pet.get("typical_gestures") or "").strip(),
    ]
    return " ".join(part for part in parts if part)


def _safe_temp_prefix(value: str) -> str:
    cleaned = "".join(ch for ch in value if ch.isalnum() or ch in {"_", "-"}).strip("._-")
    return cleaned or "build"


def _select_public_fixture(*, pet: dict[str, Any], media: list[dict[str, Any]]) -> Path:
    haystack = " ".join(
        [
            str(pet.get("pet_name") or ""),
            *[str(item.get("storage_ref") or "") for item in media],
            *[str(item.get("original_filename") or "") for item in media],
        ]
    ).lower()
    if "yinengjing" in haystack or "一能静" in haystack or "mimi" in haystack:
        return YI_NENG_JING_FIXTURE
    return ZHOU_LIU_FIXTURE


def _demo_contact_sheet(*, display_name: str, spritesheet: str) -> str:
    rows = "".join(f"<li>{action}</li>" for action in ALLOWED_ACTIONS)
    return f"""<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8" />
<title>{display_name} production preview</title>
<body>
  <h1>{display_name} 本地生产模式预览</h1>
  <p>公开演示素材，仅用于 production-mode 链路验证。</p>
  <img src="{spritesheet}" alt="{display_name} spritesheet preview" />
  <ul>{rows}</ul>
</body>
</html>
"""
