from __future__ import annotations

import json
import re
import shutil
from pathlib import Path
from typing import Any


DERIVED_METADATA_KEY = "derived_advanced"
DERIVED_KIND = "official_package_baked"

SUPPORTED_PACKAGE_MODES = {"read_only", "editable_project"}

SLOT_FILENAMES = {
    "ext_skin_c": "EXT_SKIN_C.png",
    "ext_skin_d": "EXT_Skin_D.png",
    "ext_skin_r": "EXT_Skin_R.png",
    "ext_skin_m": "EXT_Skin_M.png",
    "ext_skin_cc": "EXT_Skin_CC.png",
    "ext_skin_cr": "EXT_Skin_CR.png",
    "ext_banner_c": "EXT_Banner_C.png",
}


def conversion_policy(package_mode: Any) -> dict[str, Any]:
    mode = str(package_mode or "read_only").strip().lower()
    if mode not in SUPPORTED_PACKAGE_MODES:
        return {
            "allowed": False,
            "packageMode": mode,
            "artworkLocked": True,
            "reason": f"Unsupported Official package mode for Advanced conversion: {mode or 'missing'}",
        }
    artwork_locked = mode == "read_only"
    return {
        "allowed": True,
        "packageMode": mode,
        "artworkLocked": artwork_locked,
        "targetProjectState": "none" if artwork_locked else "editable",
        "reason": (
            "Read-only Official artwork is preserved as a locked baked base; Advanced tyre, driver and numberplate add-ons remain editable."
            if artwork_locked
            else "Editable Official projects can be converted into editable Advanced projects."
        ),
    }


def metadata_for_conversion(
    *,
    package_mode: str,
    source_skin: dict[str, Any] | None = None,
    source_package: str | Path | None = None,
) -> dict[str, Any]:
    policy = conversion_policy(package_mode)
    skin = source_skin if isinstance(source_skin, dict) else {}
    return {
        "kind": DERIVED_KIND,
        "sourceProjectType": "official_external_livery",
        "sourcePackageMode": policy.get("packageMode") or "read_only",
        "sourceSkinKey": str(skin.get("key") or "").strip(),
        "sourceSkinId": str(skin.get("skinId") or skin.get("skin_id") or "").strip(),
        "sourceCarGuid": str(skin.get("carGuid") or skin.get("car_guid") or "").strip(),
        "sourcePackage": Path(source_package).name if source_package else "",
        "artworkLocked": policy.get("artworkLocked") is True,
        "allowAdvancedDependencies": True,
        "allowedDependencyEdits": ["tyre_brands", "driver_profiles", "numberplates"],
    }


def apply_read_only_entry_policy(
    entry: dict[str, Any],
    *,
    source_skin: dict[str, Any] | None = None,
    source_package: str | Path | None = None,
) -> dict[str, Any]:
    data = dict(entry)
    data[DERIVED_METADATA_KEY] = metadata_for_conversion(
        package_mode="read_only",
        source_skin=source_skin,
        source_package=source_package,
    )
    data["origin"] = "package_import"
    data["project_state"] = "none"
    data["editable_project"] = False
    data["active"] = False
    return data


def is_official_derived(entry: dict[str, Any] | None) -> bool:
    data = entry if isinstance(entry, dict) else {}
    derived = data.get(DERIVED_METADATA_KEY) if isinstance(data.get(DERIVED_METADATA_KEY), dict) else {}
    return str(derived.get("kind") or "").strip().lower() == DERIVED_KIND


def artwork_locked(entry: dict[str, Any] | None) -> bool:
    data = entry if isinstance(entry, dict) else {}
    derived = data.get(DERIVED_METADATA_KEY) if isinstance(data.get(DERIVED_METADATA_KEY), dict) else {}
    return is_official_derived(data) and derived.get("artworkLocked") is True


def allows_advanced_dependencies(entry: dict[str, Any] | None) -> bool:
    data = entry if isinstance(entry, dict) else {}
    derived = data.get(DERIVED_METADATA_KEY) if isinstance(data.get(DERIVED_METADATA_KEY), dict) else {}
    return is_official_derived(data) and derived.get("allowAdvancedDependencies") is True


def uses_locked_project_workspace(entry: dict[str, Any] | None) -> bool:
    return artwork_locked(entry)


def safe_advanced_content_ref(value: Any, label: str) -> str:
    text = str(value or "").strip().replace("/", "\\")
    parts = [part for part in text.split("\\") if part]
    if (
        not text
        or text.startswith("\\")
        or re.match(r"^[A-Za-z]:", text)
        or ".." in parts
        or [part.lower() for part in parts[:2]] != ["content", "cars"]
    ):
        raise ValueError(f"Official package {label} is not a safe Advanced car-content reference: {text or 'missing'}")
    return "\\".join(parts)


def _read_external_livery(path: Path) -> dict[str, Any]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception as exc:
        raise ValueError(f"Official external_livery.json could not be read: {exc}") from exc
    if not isinstance(data, dict):
        raise ValueError("Official external_livery.json must contain a JSON object")
    return data


def _safe_payload_file(payload_root: Path, value: Any) -> Path | None:
    text = str(value or "").strip().replace("\\", "/")
    if not text:
        return None
    candidate = (payload_root / Path(*[part for part in text.split("/") if part])).resolve()
    try:
        candidate.relative_to(payload_root.resolve())
    except ValueError as exc:
        raise ValueError(f"Official artwork path escapes the package payload: {text}") from exc
    return candidate if candidate.is_file() else None


def discover_baked_artwork(payload_root: str | Path) -> dict[str, Path]:
    root = Path(payload_root).expanduser()
    manifest = root / "external_livery.json"
    if not manifest.is_file():
        raise FileNotFoundError(f"Official package payload is missing external_livery.json: {root}")
    data = _read_external_livery(manifest)
    found: dict[str, Path] = {}
    materials = data.get("materials") if isinstance(data.get("materials"), list) else []
    for material in materials:
        if not isinstance(material, dict):
            continue
        batch = str(material.get("batch_name") or "").strip().upper()
        base = material.get("base") if isinstance(material.get("base"), dict) else {}
        if batch.startswith("EXT_SKIN"):
            refs = {
                "ext_skin_c": base.get("base_color_replacement_texture_path"),
                "ext_skin_r": base.get("roughness_replacement_texture_path"),
                "ext_skin_m": base.get("metalness_replacement_texture_path"),
                "ext_skin_cc": base.get("clearcoat_replacement_texture_path"),
                "ext_skin_cr": base.get("clearcoat_roughness_replacement_texture_path"),
                "ext_skin_d": material.get("splat_map_replacement_texture_path"),
            }
        elif batch == "EXT_BANNER":
            refs = {"ext_banner_c": base.get("base_color_replacement_texture_path")}
        else:
            refs = {}
        for slot, ref in refs.items():
            if slot in found:
                continue
            source = _safe_payload_file(root, ref)
            if source is not None:
                found[slot] = source
    lower_files = {path.name.lower(): path for path in root.iterdir() if path.is_file()}
    for slot, filename in SLOT_FILENAMES.items():
        if slot not in found and filename.lower() in lower_files:
            found[slot] = lower_files[filename.lower()]
    return found


def copy_baked_artwork(payload_root: str | Path, project_root: str | Path) -> dict[str, Any]:
    target_root = Path(project_root).expanduser()
    target_root.mkdir(parents=True, exist_ok=True)
    sources = discover_baked_artwork(payload_root)
    copied: list[dict[str, str]] = []
    for slot, source in sources.items():
        filename = SLOT_FILENAMES[slot]
        target = target_root / filename
        shutil.copy2(source, target)
        copied.append({"slot": slot, "source": str(source), "target": str(target), "filename": filename})
    if "ext_skin_c" not in sources:
        raise FileNotFoundError("Official package does not contain a usable EXT_Skin_C artwork texture")
    return {"copied": copied, "copiedCount": len(copied), "slots": sorted(sources)}


def copy_editable_layer_studio_sources(source_project: str | Path, target_project: str | Path) -> dict[str, Any]:
    source = Path(source_project).expanduser() / "layer-studio"
    target = Path(target_project).expanduser() / "layer-studio"
    if not source.is_dir():
        return {"copied": False, "fileCount": 0, "source": str(source), "target": str(target)}
    if target.exists():
        shutil.rmtree(target)
    shutil.copytree(source, target)
    count = sum(1 for path in target.rglob("*") if path.is_file())
    return {"copied": bool(count), "fileCount": count, "source": str(source), "target": str(target)}
