from __future__ import annotations

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

from liverylab_core import advanced_content_setup, package_state, runtime_switch


MANIFEST_FILENAME = "evo_update_workflow.json"
FORMAT_VERSION = 1
EXTRACTED_TOP_LEVEL_DIR_NAMES = ("cfg", "content", "editor", "serverconfig", "system", "uiresources")
PHASE_DETECTED = "detected"
PHASE_CLEANUP = "cleanup"
PHASE_EXTRACT = "extract"
PHASE_FINALIZE = "finalize"
PHASE_COMPLETE = "complete"


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


def _same_path(left: Any, right: Any) -> bool:
    return str(left or "").replace("\\", "/").rstrip("/").casefold() == str(right or "").replace("\\", "/").rstrip("/").casefold()


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


def _read(state_root: str | Path) -> dict[str, Any]:
    path = manifest_path(state_root)
    if 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(state_root: str | Path, data: dict[str, Any]) -> dict[str, Any]:
    path = manifest_path(state_root)
    path.parent.mkdir(parents=True, exist_ok=True)
    temp = path.with_name(f"{path.name}.tmp")
    next_data = dict(data)
    next_data["formatVersion"] = FORMAT_VERSION
    next_data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    temp.write_text(json.dumps(next_data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    temp.replace(path)
    return next_data


def _cleanup_status(root: Path, obsolete_path: str = "") -> dict[str, Any]:
    obsolete = Path(obsolete_path) if str(obsolete_path).strip() else None
    folders = [
        {"name": name, "path": str(root / name), "exists": (root / name).is_dir()}
        for name in EXTRACTED_TOP_LEVEL_DIR_NAMES
    ]
    remaining = [row for row in folders if row["exists"]]
    obsolete_exists = bool(obsolete and obsolete.is_file())
    active_path = root / package_state.CONTENT_PACKAGE_NAME
    return {
        "obsoletePackagePath": str(obsolete or ""),
        "obsoletePackageExists": obsolete_exists,
        "activePackagePath": str(active_path),
        "activePackageExists": active_path.is_file(),
        "folders": folders,
        "remainingFolders": remaining,
        "remainingFolderCount": len(remaining),
        "ready": bool(not obsolete_exists and not remaining and active_path.is_file()),
    }


def status(
    state_root: str | Path,
    game_root: str | Path,
    *,
    package_status: dict[str, Any] | None = None,
    advanced_setup_status: dict[str, Any] | None = None,
) -> dict[str, Any]:
    root = _resolve(game_root)
    package = package_status or package_state.content_package_status(root, state_root)
    setup = advanced_setup_status or advanced_content_setup.status(state_root, root, package)
    guidance = runtime_switch.package_update_conflict_guidance(package, setup)
    stored = _read(state_root)
    active_state = bool(stored and _same_path(stored.get("root"), root) and stored.get("phase") != PHASE_COMPLETE)
    if active_state:
        phase = str(stored.get("phase") or PHASE_DETECTED)
        obsolete_path = str(stored.get("obsoletePackagePath") or "")
    else:
        phase = PHASE_DETECTED
        obsolete_path = str(guidance.get("obsoleteDisabledPath") or "")
        stored = {}
    cleanup = _cleanup_status(root, obsolete_path)
    available = bool(active_state or guidance.get("manualResolutionSupported"))
    return {
        "available": available,
        "shouldPrompt": bool(available and phase != PHASE_COMPLETE),
        "phase": phase,
        "root": str(root),
        "manifestPath": str(manifest_path(state_root)),
        "workflow": stored,
        "package": package,
        "advancedContentSetup": setup,
        "conflict": guidance,
        "cleanup": cleanup,
        "activeLiveries": list(stored.get("activeLiveries") or []),
    }


def start(
    state_root: str | Path,
    game_root: str | Path,
    active_liveries: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    root = _resolve(game_root)
    current = status(state_root, root)
    existing = current.get("workflow") if isinstance(current.get("workflow"), dict) else {}
    if existing and current.get("shouldPrompt"):
        return current
    conflict = current.get("conflict") if isinstance(current.get("conflict"), dict) else {}
    if conflict.get("manualResolutionSupported") is not True:
        raise RuntimeError("A single LiveryLab-managed obsolete content package was not identified.")
    now = datetime.now().isoformat(timespec="seconds")
    data = _write(
        state_root,
        {
            "formatVersion": FORMAT_VERSION,
            "phase": PHASE_DETECTED,
            "root": str(root),
            "createdAt": now,
            "preparedAt": "",
            "extractedAt": "",
            "completedAt": "",
            "targetWorkflow": "",
            "obsoletePackagePath": str(conflict.get("obsoleteDisabledPath") or ""),
            "activePackagePath": str(conflict.get("activePath") or root / package_state.CONTENT_PACKAGE_NAME),
            "activePackage": dict(conflict.get("activePackage") or {}),
            "obsoletePackage": dict(conflict.get("obsoleteDisabledPackage") or {}),
            "activeLiveries": [dict(row) for row in active_liveries or [] if isinstance(row, dict)],
            "preparation": {},
            "restoredLiveries": [],
        },
    )
    return status(state_root, root, package_status=current.get("package"), advanced_setup_status=current.get("advancedContentSetup")) | {"workflow": data}


def mark_prepared(state_root: str | Path, game_root: str | Path, preparation: dict[str, Any]) -> dict[str, Any]:
    root = _resolve(game_root)
    data = _read(state_root)
    if not data or not _same_path(data.get("root"), root):
        raise RuntimeError("The EVO update workflow was not started for this game folder.")
    data["phase"] = PHASE_CLEANUP
    data["preparedAt"] = datetime.now().isoformat(timespec="seconds")
    data["preparation"] = dict(preparation or {})
    _write(state_root, data)
    return status(state_root, root)


def inspect_cleanup(state_root: str | Path, game_root: str | Path) -> dict[str, Any]:
    current = status(state_root, game_root)
    if not current.get("workflow"):
        raise RuntimeError("The EVO update workflow was not started.")
    cleanup = current["cleanup"]
    if cleanup.get("ready") and current.get("phase") == PHASE_CLEANUP:
        data = dict(current["workflow"])
        data["phase"] = PHASE_EXTRACT
        _write(state_root, data)
        current = status(state_root, game_root)
    return current


def mark_extracted(state_root: str | Path, game_root: str | Path) -> dict[str, Any]:
    root = _resolve(game_root)
    current = status(state_root, root)
    if not current.get("workflow"):
        raise RuntimeError("The EVO update workflow was not started.")
    setup = current.get("advancedContentSetup") if isinstance(current.get("advancedContentSetup"), dict) else {}
    if setup.get("ready") is not True:
        raise RuntimeError(str(setup.get("reason") or "The updated content extraction is not current."))
    data = dict(current["workflow"])
    data["phase"] = PHASE_FINALIZE
    data["extractedAt"] = datetime.now().isoformat(timespec="seconds")
    _write(state_root, data)
    return status(state_root, root)


def complete(
    state_root: str | Path,
    game_root: str | Path,
    target_workflow: str,
    restored_liveries: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    root = _resolve(game_root)
    data = _read(state_root)
    if not data or not _same_path(data.get("root"), root):
        raise RuntimeError("The EVO update workflow was not started for this game folder.")
    target = str(target_workflow or "").strip().lower()
    if target not in {"advanced", "official"}:
        raise ValueError("The target workflow must be advanced or official.")
    data["phase"] = PHASE_COMPLETE
    data["targetWorkflow"] = target
    data["restoredLiveries"] = [dict(row) for row in restored_liveries or [] if isinstance(row, dict)]
    data["completedAt"] = datetime.now().isoformat(timespec="seconds")
    saved = _write(state_root, data)
    return {"completed": True, "phase": PHASE_COMPLETE, "root": str(root), "workflow": saved}
