from __future__ import annotations

import json
import os
from datetime import datetime
from pathlib import Path
from typing import Any


MANIFEST_FILENAME = "advanced_content_setup.json"
FORMAT_VERSION = 1
CONTENT_PACKAGE_NAME = "content.kspkg"
ACTIVE_LIVERY_REFRESH_KEY = "activeLiveryRefresh"


def _resolve(path: str | Path) -> Path:
    candidate = Path(path).expanduser()
    try:
        return candidate.resolve()
    except OSError:
        return candidate.absolute()


def manifest_path(manifest_root: str | Path) -> Path:
    return _resolve(manifest_root) / MANIFEST_FILENAME


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


def _write_manifest(manifest_root: str | Path, data: dict[str, Any]) -> Path:
    path = manifest_path(manifest_root)
    path.parent.mkdir(parents=True, exist_ok=True)
    temp = path.with_name(f"{path.name}.tmp")
    temp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    temp.replace(path)
    return path


def _file_signature(path: Path) -> dict[str, Any]:
    stat = path.stat()
    return {
        "path": str(path),
        "name": path.name,
        "size": int(stat.st_size),
        "mtimeNs": int(stat.st_mtime_ns),
    }


def _same_path(left: str | Path, right: str | Path) -> bool:
    try:
        return _resolve(left) == _resolve(right)
    except Exception:
        return os.path.normcase(str(left)) == os.path.normcase(str(right))


def record_success(
    manifest_root: str | Path,
    game_root: str | Path,
    pack_path: str | Path,
    output_dir: str | Path,
    extraction_result: dict[str, Any],
) -> dict[str, Any]:
    root = _resolve(game_root)
    package = _resolve(pack_path)
    output = _resolve(output_dir)
    if package.parent != root or package.name.lower() != CONTENT_PACKAGE_NAME:
        return {"recorded": False, "reason": "not the root content.kspkg", "manifestPath": str(manifest_path(manifest_root))}
    if output != root:
        return {"recorded": False, "reason": "output is not the AC EVO root", "manifestPath": str(manifest_path(manifest_root))}
    if int(extraction_result.get("entry_count") or 0) <= 0 or int(extraction_result.get("file_count") or 0) <= 0:
        raise ValueError("A completed full package extraction is required before Advanced runtime can be enabled.")
    content_cars = root / "content" / "cars"
    if not content_cars.exists() or not content_cars.is_dir():
        raise FileNotFoundError(f"Extracted content/cars was not created: {content_cars}")
    completed_at = datetime.now().isoformat(timespec="seconds")
    data = {
        "formatVersion": FORMAT_VERSION,
        "completed": True,
        "completedAt": completed_at,
        "invalidatedAt": "",
        "invalidatedReason": "",
        "root": str(root),
        "package": _file_signature(package),
        "extraction": {
            "entryCount": int(extraction_result.get("entry_count") or 0),
            "fileCount": int(extraction_result.get("file_count") or 0),
            "directoryCount": int(extraction_result.get("directory_count") or 0),
            "bytesProcessed": int(extraction_result.get("bytes_processed") or 0),
            "elapsedSeconds": float(extraction_result.get("elapsed_seconds") or 0),
        },
        ACTIVE_LIVERY_REFRESH_KEY: {
            "state": "pending",
            "extractionId": completed_at,
            "createdAt": completed_at,
            "updatedAt": completed_at,
            "items": {},
            "summary": {"total": 0, "refreshed": 0, "failed": 0, "pending": 0},
        },
    }
    path = _write_manifest(manifest_root, data)
    return {"recorded": True, "ready": True, "reason": "full extraction completed", "manifestPath": str(path), "manifest": data}


def record_legacy_ready(
    manifest_root: str | Path,
    game_root: str | Path,
    package_status: dict[str, Any],
) -> dict[str, Any]:
    root = _resolve(game_root)
    current = status(manifest_root, root, package_status)
    if current.get("ready") is not True or current.get("legacy") is not True:
        raise ValueError("Only a currently ready legacy Advanced extraction can be recorded.")
    package = _current_package_path(package_status)
    if package is None:
        raise FileNotFoundError("The content package for the legacy Advanced extraction is missing.")
    completed_at = datetime.now().isoformat(timespec="seconds")
    data = {
        "formatVersion": FORMAT_VERSION,
        "completed": True,
        "completedAt": completed_at,
        "invalidatedAt": "",
        "invalidatedReason": "",
        "root": str(root),
        "package": _file_signature(package),
        "extraction": {
            "entryCount": 0,
            "fileCount": 0,
            "directoryCount": 0,
            "bytesProcessed": 0,
            "elapsedSeconds": 0.0,
            "legacyAdopted": True,
        },
        ACTIVE_LIVERY_REFRESH_KEY: {
            "state": "completed",
            "extractionId": completed_at,
            "createdAt": completed_at,
            "updatedAt": completed_at,
            "items": {},
            "summary": {"total": 0, "refreshed": 0, "failed": 0, "pending": 0},
        },
    }
    path = _write_manifest(manifest_root, data)
    return {
        "recorded": True,
        "ready": True,
        "reason": "existing LiveryLab-managed extracted content was recorded",
        "manifestPath": str(path),
        "manifest": data,
    }


def active_livery_refresh_status(
    manifest_root: str | Path,
    game_root: str | Path,
) -> dict[str, Any]:
    root = _resolve(game_root)
    data = _read_manifest(manifest_root)
    if not data or data.get("completed") is not True or data.get("invalidatedAt"):
        return {
            "required": False,
            "state": "not_required",
            "reason": "No completed Advanced full extraction is recorded.",
            "extractionId": "",
            "items": {},
            "summary": {"total": 0, "refreshed": 0, "failed": 0, "pending": 0},
        }
    if not _same_path(data.get("root") or "", root):
        return {
            "required": False,
            "state": "not_required",
            "reason": "The completed Advanced extraction belongs to a different AC EVO root.",
            "extractionId": "",
            "items": {},
            "summary": {"total": 0, "refreshed": 0, "failed": 0, "pending": 0},
        }
    extraction_id = str(data.get("completedAt") or "").strip()
    refresh = data.get(ACTIVE_LIVERY_REFRESH_KEY)
    if not isinstance(refresh, dict) or str(refresh.get("extractionId") or "") != extraction_id:
        refresh = {
            "state": "pending",
            "extractionId": extraction_id,
            "createdAt": extraction_id,
            "updatedAt": extraction_id,
            "items": {},
            "summary": {"total": 0, "refreshed": 0, "failed": 0, "pending": 0},
        }
    result = dict(refresh)
    result["required"] = str(result.get("state") or "pending") != "completed"
    result["items"] = dict(result.get("items") or {}) if isinstance(result.get("items"), dict) else {}
    result["summary"] = dict(result.get("summary") or {}) if isinstance(result.get("summary"), dict) else {}
    return result


def save_active_livery_refresh(
    manifest_root: str | Path,
    game_root: str | Path,
    refresh: dict[str, Any],
    *,
    expected_extraction_id: str,
) -> dict[str, Any]:
    root = _resolve(game_root)
    data = _read_manifest(manifest_root)
    extraction_id = str(data.get("completedAt") or "").strip()
    if data.get("completed") is not True or data.get("invalidatedAt"):
        raise RuntimeError("Cannot save the active-livery refresh without a completed Advanced extraction.")
    if not _same_path(data.get("root") or "", root):
        raise RuntimeError("Cannot save the active-livery refresh for a different AC EVO root.")
    if not extraction_id or extraction_id != str(expected_extraction_id or "").strip():
        raise RuntimeError("The Advanced extraction changed while active liveries were being refreshed.")
    now = datetime.now().isoformat(timespec="seconds")
    next_refresh = dict(refresh or {})
    next_refresh["extractionId"] = extraction_id
    next_refresh["createdAt"] = str(next_refresh.get("createdAt") or extraction_id)
    next_refresh["updatedAt"] = now
    next_refresh["items"] = dict(next_refresh.get("items") or {}) if isinstance(next_refresh.get("items"), dict) else {}
    next_refresh["summary"] = dict(next_refresh.get("summary") or {}) if isinstance(next_refresh.get("summary"), dict) else {}
    data[ACTIVE_LIVERY_REFRESH_KEY] = next_refresh
    _write_manifest(manifest_root, data)
    result = dict(next_refresh)
    result["required"] = str(result.get("state") or "pending") != "completed"
    return result


def invalidate(manifest_root: str | Path, game_root: str | Path, reason: str) -> dict[str, Any]:
    root = _resolve(game_root)
    data = _read_manifest(manifest_root)
    if data and not _same_path(data.get("root") or "", root):
        data = {}
    data.update(
        {
            "formatVersion": FORMAT_VERSION,
            "completed": False,
            "invalidatedAt": datetime.now().isoformat(timespec="seconds"),
            "invalidatedReason": str(reason or "Advanced content setup was reset."),
            "root": str(root),
        }
    )
    path = _write_manifest(manifest_root, data)
    return {"invalidated": True, "ready": False, "reason": data["invalidatedReason"], "manifestPath": str(path), "manifest": data}


def _current_package_path(package_status: dict[str, Any]) -> Path | None:
    for key in ("activePath", "disabledPath"):
        value = str(package_status.get(key) or "").strip()
        if not value:
            continue
        path = Path(value)
        if path.exists() and path.is_file():
            return path
    return None


def _legacy_ready(root: Path, package_status: dict[str, Any]) -> bool:
    if not (root / "content" / "cars").is_dir():
        return False
    if package_status.get("disabledExists"):
        return True
    package_manifest = package_status.get("manifest") if isinstance(package_status.get("manifest"), dict) else {}
    return bool(
        package_manifest.get("action") == "disabled_content_package"
        and _same_path(package_manifest.get("root") or "", root)
    )


def status(
    manifest_root: str | Path,
    game_root: str | Path,
    package_status: dict[str, Any] | None = None,
) -> dict[str, Any]:
    root = _resolve(game_root)
    package = dict(package_status or {})
    path = manifest_path(manifest_root)
    data = _read_manifest(manifest_root)
    content_cars_exists = (root / "content" / "cars").is_dir()
    base = {
        "ready": False,
        "completed": False,
        "legacy": False,
        "root": str(root),
        "contentCarsExists": content_cars_exists,
        "manifestPath": str(path),
        "manifestExists": path.exists(),
        "manifest": data,
        "reason": "Advanced full-content extraction has not completed.",
    }
    if data:
        if not _same_path(data.get("root") or "", root):
            base["reason"] = "The completed Advanced extraction belongs to a different AC EVO root."
            return base
        if data.get("completed") is not True or data.get("invalidatedAt"):
            base["reason"] = str(data.get("invalidatedReason") or "Advanced content setup was reset.")
            return base
        if not content_cars_exists:
            base["reason"] = "The extracted content/cars folder is missing."
            return base
        current_package = _current_package_path(package)
        recorded = data.get("package") if isinstance(data.get("package"), dict) else {}
        if current_package is None:
            base["reason"] = "content.kspkg and its LiveryLab-disabled copy are missing."
            return base
        current_signature = _file_signature(current_package)
        if (
            int(recorded.get("size") or -1) != current_signature["size"]
            or int(recorded.get("mtimeNs") or -1) != current_signature["mtimeNs"]
        ):
            base["reason"] = (
                "An updated or different content.kspkg was detected after the last full Advanced extraction. "
                "Run Extract all again before enabling the Advanced runtime."
            )
            return base
        base.update({"ready": True, "completed": True, "reason": "Advanced full-content extraction is current.", "package": current_signature})
        return base
    if _legacy_ready(root, package):
        base.update(
            {
                "ready": True,
                "completed": True,
                "legacy": True,
                "reason": "Existing LiveryLab-managed extracted content was recognized.",
            }
        )
    return base
