from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from . import official_car_resources, preview_assets


def _as_path(value: str | Path | None) -> Path | None:
    if value is None:
        return None
    text = str(value).strip()
    return Path(text).expanduser() if text else None


def _path_info(path: Path | None) -> dict[str, Any]:
    exists = bool(path and path.exists())
    return {
        "path": str(path) if path else "",
        "exists": exists,
        "isFile": bool(exists and path and path.is_file()),
        "isDir": bool(exists and path and path.is_dir()),
    }


def _read_json(path: Path) -> tuple[dict[str, Any], str]:
    if not path.exists() or not path.is_file():
        return {}, ""
    try:
        data = json.loads(path.read_text(encoding="utf-8-sig"))
    except Exception as exc:
        return {}, str(exc)
    return data if isinstance(data, dict) else {}, ""


def _manifest_path_value(base: Path, manifest: dict[str, Any], key: str, fallback: str) -> Path:
    value = str(manifest.get(key) or "").strip()
    if value:
        candidate = Path(value)
        return candidate if candidate.is_absolute() else base / candidate
    return base / fallback


def _first_existing_glb(folder: Path) -> Path | None:
    if not folder.exists() or not folder.is_dir():
        return None
    direct = folder / "exterior.glb"
    if direct.exists() and direct.is_file():
        return direct
    try:
        return next((path for path in folder.rglob("*.glb") if path.is_file()), None)
    except Exception:
        return None


def _first_existing_uv(folder: Path) -> Path | None:
    if not folder.exists() or not folder.is_dir():
        return None
    candidates = (folder / "EXT_SKIN_UV.png", folder / "ext_skin_uv.png")
    for candidate in candidates:
        if candidate.exists() and candidate.is_file():
            return candidate
    try:
        return next((path for path in folder.rglob("*uv*.png") if path.is_file()), None)
    except Exception:
        return None


def _official_status(preview_assets_root: str | Path | None, car_id: str) -> dict[str, Any]:
    root = _as_path(preview_assets_root)
    if root is None:
        return {
            "source": "official_preview_assets",
            "ready": False,
            "modelReady": False,
            "root": "",
            "missing": ["official preview assets root unavailable"],
        }
    derived_root = official_car_resources.derived_car_root(root, car_id)
    manifest_path = official_car_resources.derived_manifest_path(root, car_id)
    manifest, manifest_error = _read_json(manifest_path)
    glb_path = _manifest_path_value(derived_root, manifest, "glbPath", "exterior.glb")
    uv_path = _manifest_path_value(derived_root, manifest, "uvTemplatePath", "EXT_SKIN_UV.png")
    if not glb_path.exists():
        fallback = _first_existing_glb(derived_root)
        if fallback:
            glb_path = fallback
    if not uv_path.exists():
        fallback_uv = _first_existing_uv(derived_root)
        if fallback_uv:
            uv_path = fallback_uv
    model_ready = bool(glb_path.exists() and glb_path.is_file())
    uv_ready = bool(uv_path.exists() and uv_path.is_file())
    missing: list[str] = []
    if not derived_root.exists():
        missing.append("official derived preview asset folder")
    if not model_ready:
        missing.append("official exterior GLB")
    if not uv_ready:
        missing.append("official UV template PNG")
    return {
        "source": "official_preview_assets",
        "ready": model_ready,
        "modelReady": model_ready,
        "uvReady": uv_ready,
        "root": str(derived_root),
        "manifestPath": str(manifest_path),
        "manifestExists": manifest_path.exists() and manifest_path.is_file(),
        "manifestError": manifest_error,
        "manifestReady": manifest.get("ready") is True,
        "model": _path_info(glb_path),
        "uvTemplate": _path_info(uv_path),
        "missing": missing,
    }


def _local_status(livery_library_root: str | Path | None, car_id: str) -> dict[str, Any]:
    root = _as_path(livery_library_root)
    if root is None:
        return {
            "source": "local_preview_assets",
            "ready": False,
            "modelReady": False,
            "root": "",
            "missing": ["local livery library root unavailable"],
        }
    status = preview_assets.status(root, car_id)
    candidates = status.get("candidates") if isinstance(status.get("candidates"), list) else [status]
    selected_folder = Path(str(status.get("root") or preview_assets.derived_car_root(root, car_id)))
    glb_path = _first_existing_glb(selected_folder)
    uv_path = _first_existing_uv(selected_folder)
    if glb_path is None or uv_path is None:
        for candidate in candidates:
            if not isinstance(candidate, dict):
                continue
            folder = Path(str(candidate.get("root") or ""))
            if glb_path is None:
                glb_path = _first_existing_glb(folder)
            if uv_path is None:
                uv_path = _first_existing_uv(folder)
            if glb_path is not None and uv_path is not None:
                break
    model_ready = bool(glb_path and glb_path.exists() and glb_path.is_file())
    uv_ready = bool(uv_path and uv_path.exists() and uv_path.is_file())
    missing = list(status.get("missing") if isinstance(status.get("missing"), list) else [])
    if not model_ready and "local exterior GLB" not in missing:
        missing.append("local exterior GLB")
    return {
        "source": "local_preview_assets",
        "ready": model_ready,
        "modelReady": model_ready,
        "uvReady": uv_ready,
        "root": str(selected_folder),
        "status": status,
        "model": _path_info(glb_path),
        "uvTemplate": _path_info(uv_path),
        "missing": missing,
    }


def _game_root_status(game_root: str | Path | None, car_id: str) -> dict[str, Any]:
    root = _as_path(game_root)
    if root is None:
        return {
            "source": "game_root",
            "ready": False,
            "canPrepare": False,
            "root": "",
            "carContentPath": "",
            "missing": ["game root unavailable"],
        }
    car_content = root / "content" / "cars" / car_id
    content_ready = bool(car_content.exists() and car_content.is_dir())
    package_path = root / "content.kspkg"
    can_prepare = content_ready or bool(package_path.exists() and package_path.is_file())
    missing: list[str] = []
    if not content_ready:
        missing.append("extracted car content folder")
    if not package_path.exists():
        missing.append("content.kspkg")
    return {
        "source": "game_root",
        "ready": False,
        "canPrepare": can_prepare,
        "root": str(root),
        "carContentPath": str(car_content),
        "carContentExists": content_ready,
        "contentPackagePath": str(package_path),
        "contentPackageExists": package_path.exists() and package_path.is_file(),
        "missing": missing,
    }


def inspect_template_preview_assets(
    *,
    car_id: Any,
    cardesign_id: Any = "",
    workflow: Any = "",
    game_root: str | Path | None = None,
    official_preview_assets_root: str | Path | None = None,
    livery_library_root: str | Path | None = None,
) -> dict[str, Any]:
    clean_car_id = official_car_resources.safe_car_id(car_id, "")
    workflow_text = str(workflow or "").strip().lower()
    if not clean_car_id:
        return {
            "ready": False,
            "carId": "",
            "cardesignId": str(cardesign_id or "").strip(),
            "workflow": workflow_text,
            "source": "",
            "canPrepare": False,
            "missing": ["car id"],
            "sources": {},
            "warnings": ["Car id is required for template preview assets."],
        }

    official = _official_status(official_preview_assets_root, clean_car_id)
    local = _local_status(livery_library_root, clean_car_id)
    game = _game_root_status(game_root, clean_car_id)
    sources = {
        "official": official,
        "local": local,
        "gameRoot": game,
    }

    preferred: list[dict[str, Any]]
    alternate: list[dict[str, Any]]
    if workflow_text in {"official", "official_external", "official_external_livery"}:
        preferred = [official]
        alternate = [local]
    elif workflow_text in {"advanced", "advanced_creator", "local"}:
        preferred = [local]
        alternate = [official]
    else:
        preferred = [local, official]
        alternate = []
    selected = next((item for item in preferred if item.get("ready")), None)

    warnings: list[str] = []
    if selected is None and any(item.get("ready") for item in alternate):
        warnings.append("Preview assets are available in another workflow, but not in the requested workflow.")
    if selected is None and official.get("ready") and not local.get("ready"):
        warnings.append("Official preview assets are available, but local/advanced preview assets are not ready.")
    if selected is None and local.get("ready") and not official.get("ready"):
        warnings.append("Local/advanced preview assets are available, but official preview assets are not ready.")
    if selected is None and game.get("canPrepare"):
        warnings.append("Preview assets can be prepared from available game content, but no converted GLB is ready yet.")
    if selected is None and not game.get("canPrepare"):
        warnings.append("No ready GLB was found and no game content source is available for preparation.")

    ready = bool(selected and selected.get("ready"))
    model = selected.get("model") if isinstance(selected, dict) and isinstance(selected.get("model"), dict) else {}
    uv_template = selected.get("uvTemplate") if isinstance(selected, dict) and isinstance(selected.get("uvTemplate"), dict) else {}
    return {
        "ready": ready,
        "modelReady": ready,
        "carId": clean_car_id,
        "cardesignId": str(cardesign_id or "").strip(),
        "workflow": workflow_text,
        "source": str(selected.get("source") or "") if selected else "",
        "model": model,
        "uvTemplate": uv_template,
        "canPrepare": bool(game.get("canPrepare")),
        "prepareSource": "game_root" if game.get("canPrepare") else "",
        "prepareReason": "game content or content package is available" if game.get("canPrepare") else "no game content source found",
        "sources": sources,
        "missing": [] if ready else list(dict.fromkeys([
            missing
            for item in preferred
            for missing in (item.get("missing") or [])
        ])),
        "warnings": warnings,
    }
