from __future__ import annotations

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

from liverylab_core import ai_vehicle_spec, grid_bridge
from liverylab_core.game_write_guard import path_is_within, require_game_write_ready


APP_STORAGE_DIR_NAMES = {"liverylab", "liverylibrary", "liveryprojects", "liverypackageexport", "userdata"}


def norm_slashes(value: str) -> str:
    return str(value or "").replace("/", "\\")


def split_content_path(value: str) -> list[str]:
    return [part for part in norm_slashes(value).split("\\") if part]


def normalize_game_rel_path(value: Any) -> str:
    parts = split_content_path(str(value or "").strip())
    lower_parts = [part.lower() for part in parts]
    for anchor in ("content", "system", "uiresources"):
        if anchor in lower_parts:
            index = lower_parts.index(anchor)
            return norm_slashes("\\".join(parts[index:]))
    return norm_slashes("\\".join(parts))


def resolve_game_rel(root: Path, rel: Any) -> Path | None:
    rel_norm = normalize_game_rel_path(rel)
    if not rel_norm:
        return None
    return root / Path(*split_content_path(rel_norm))


def path_label(root: Path, path: Path) -> str:
    try:
        return norm_slashes(str(path.resolve().relative_to(root.resolve())))
    except Exception:
        return str(path)


def delete_path_allowed(root: Path, path: Path) -> bool:
    try:
        resolved = path.resolve()
    except Exception:
        resolved = path.absolute()
    if not path_is_within(resolved, root):
        return False
    parts = {part.lower() for part in resolved.parts}
    return not any(part in APP_STORAGE_DIR_NAMES for part in parts)


def add_delete_row(rows: list[dict[str, Any]], seen: set[str], root: Path, kind: str, rel: Any) -> None:
    rel_norm = normalize_game_rel_path(rel)
    if not rel_norm:
        return
    path = resolve_game_rel(root, rel_norm)
    if path is None:
        return
    key = str(path).lower()
    if key in seen:
        return
    seen.add(key)
    rows.append({"kind": kind, "rel": rel_norm, "path": path})


def collect_driver_delete_rows(entry: dict[str, Any], root: Path) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    seen: set[str] = set()
    car_content_path = normalize_game_rel_path(entry.get("car_content_path") or "")
    skin_id = str(entry.get("skin_id") or "").strip()
    if car_content_path and skin_id:
        add_delete_row(rows, seen, root, "driver_profile.driver_dir", f"{car_content_path}\\skins\\{skin_id}\\driver")
    driver_profile = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
    add_delete_row(rows, seen, root, "driver_profile.appliedDriverFinalState", driver_profile.get("appliedDriverFinalState") or "")
    return rows


def _load_tyre_manifest(path: Path) -> dict[str, Any] | None:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(data, dict) or data.get("type") != "liverylab_custom_tyre_brand":
        return None
    data["_manifestPath"] = str(path)
    return data


def collect_tyre_brand_delete_rows(root: Path, tyre_brands_root: str | Path) -> list[dict[str, Any]]:
    library_root = Path(tyre_brands_root)
    if not library_root.exists():
        return []
    rows: list[dict[str, Any]] = []
    seen: set[str] = set()
    for manifest_path in sorted(library_root.glob("*/*/manifest.json"), key=lambda item: str(item).lower()):
        manifest = _load_tyre_manifest(manifest_path)
        if not manifest:
            continue
        brand_id = str(manifest.get("id") or manifest_path.parent.name).strip()
        rels = [str(rel or "") for rel in (manifest.get("files") or []) if str(rel or "").strip()]
        tyretextures_rel = str(manifest.get("tyretexturesRel") or "").strip()
        if tyretextures_rel and tyretextures_rel not in rels:
            rels.append(tyretextures_rel)
        for rel in rels:
            add_delete_row(rows, seen, root, f"custom_tyre_brand.{brand_id}", rel)
    return rows


def delete_rows_no_backup(root: Path, rows: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    paths = [Path(str(row.get("path"))) for row in rows if row.get("path")]
    kept: list[Path] = []
    for path in sorted(paths, key=lambda item: (len(item.parts), str(item).lower())):
        try:
            if any(path != parent and path.resolve().is_relative_to(parent.resolve()) for parent in kept):
                continue
        except Exception:
            pass
        kept.append(path)

    row_by_path = {str(Path(str(row.get("path"))).resolve()).lower(): row for row in rows if row.get("path")}
    deleted: list[dict[str, Any]] = []
    missing: list[dict[str, Any]] = []
    for path in kept:
        row = row_by_path.get(str(path.resolve()).lower(), {})
        rel = path_label(root, path)
        if not path.exists():
            missing.append({"path": str(path), "rel": rel, "kind": row.get("kind") or ""})
            continue
        if not delete_path_allowed(root, path):
            raise ValueError(f"Refusing to delete path outside allowed ACE root content: {path}")
        if path.is_dir():
            shutil.rmtree(path)
            action = "delete_dir"
        elif path.is_file():
            path.unlink()
            action = "delete_file"
        else:
            missing.append({"path": str(path), "rel": rel, "kind": row.get("kind") or ""})
            continue
        deleted.append({"path": str(path), "rel": rel, "kind": row.get("kind") or "", "action": action})
    return deleted, missing


def _writer_result_count(result: dict[str, Any], statuses: set[str] | None = None) -> int:
    statuses = statuses or {"written", "deleted"}
    writer = result.get("writer") if isinstance(result.get("writer"), dict) else {}
    rows = writer.get("results") if isinstance(writer.get("results"), list) else []
    return sum(1 for row in rows if isinstance(row, dict) and str(row.get("status") or "written") in statuses)


def cleanup_grid_bridge_no_backup(root: Path) -> dict[str, Any]:
    paths = grid_bridge.paths(root)
    require_game_write_ready(root, "restore AC EVO UI hooks to default", [paths["ui"]], require_content_cars=False)
    bridge_before = grid_bridge.bridge_status(root, None)
    name_before = grid_bridge.name_cleanup_status(root)
    bridge_result = grid_bridge.remove_bridge(root, None, backup=False, require_content_cars=False)
    name_result = grid_bridge.remove_name_cleanup(root, None, backup=False, require_content_cars=False)
    bridge_dir_deleted = False
    bridge_dir = paths["bridge_dir"]
    if bridge_dir.exists() and bridge_dir.is_dir() and delete_path_allowed(root, bridge_dir):
        shutil.rmtree(bridge_dir)
        bridge_dir_deleted = True
    return {
        "bridge": bridge_result,
        "nameCleanup": name_result,
        "bridgeBefore": bridge_before,
        "nameCleanupBefore": name_before,
        "bridgeDirDeleted": bridge_dir_deleted,
        "bridgeWriteCount": int(bool(bridge_before.get("requiresCleanup"))) + int(bridge_dir_deleted),
        "nameCleanupWriteCount": int(bool(name_before.get("requiresCleanup"))),
    }


def cleanup_ai_vehicle_specs_no_backup(root: Path, library_root: str | Path) -> dict[str, Any]:
    return ai_vehicle_spec.cleanup_all(
        root,
        library_root,
        None,
        backup=False,
        require_content_cars=False,
    )
