from __future__ import annotations

import json
import os
import re
import shutil
import stat
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from liverylab_core import online_league_deployments, online_league_payload, package_zip, workflow_scopes


INSTALLED_MANIFEST = "installed_pack.json"
ENTRY_MANIFEST = "entry_manifest.json"
BACKUP_DIRNAME = "DeploymentBackups"
PACK_TYPE = "liverylabOnlineLeaguePack"
PACK_FORMAT_VERSION = 2
ACTIVATE_CONFIRM_TOKEN = "ACTIVATE_LEAGUE_PACK"
DEACTIVATE_CONFIRM_TOKEN = "DEACTIVATE_LEAGUE_PACK"
DELETE_CONFIRM_TOKEN = "DELETE_LEAGUE_PACK"
DELETE_TRANSACTION_DIRNAME = ".delete_transactions"


def _now_iso() -> str:
    return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


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


def _write_json_atomic(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    temp = path.with_suffix(path.suffix + ".tmp")
    temp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    temp.replace(path)


def _safe_id(value: Any, label: str) -> str:
    text = str(value or "").strip()
    if not text or Path(text).name != text or text in {".", ".."} or any(char in text for char in '<>:"/\\|?*'):
        raise ValueError(f"Invalid Online League {label}: {value}")
    return text


def _installed_root(library_root: str | Path, installed_pack_id: Any) -> Path:
    library = Path(library_root).expanduser()
    pack_id = _safe_id(installed_pack_id, "installed pack id")
    target = library / pack_id
    try:
        target.resolve().relative_to(library.resolve())
    except Exception as exc:
        raise ValueError("Online League installed pack is outside LeagueLibrary.") from exc
    if not target.is_dir():
        raise FileNotFoundError(f"Installed Online League pack not found: {pack_id}")
    return target


def _load_pack(library_root: str | Path, installed_pack_id: Any) -> tuple[Path, dict[str, Any], list[dict[str, Any]]]:
    pack_root = _installed_root(library_root, installed_pack_id)
    manifest = _read_json(pack_root / INSTALLED_MANIFEST)
    if manifest.get("type") != PACK_TYPE or int(manifest.get("formatVersion") or 0) != PACK_FORMAT_VERSION:
        raise ValueError("Installed Online League pack has an unsupported format.")
    if workflow_scopes.normalize_scope(manifest.get("workflowScope")) != workflow_scopes.SCOPE_OFFICIAL:
        raise ValueError("Online League activation is available only for Official packs.")
    entries = [dict(item) for item in manifest.get("entries") or [] if isinstance(item, dict)]
    if not entries or int(manifest.get("entryCount") or 0) != len(entries):
        raise ValueError("Installed Online League pack entry count is invalid.")
    return pack_root, manifest, entries


def _entry_payload(pack_root: Path, entry: dict[str, Any]) -> tuple[Path, dict[str, Any], dict[str, Any]]:
    entry_id = _safe_id(entry.get("id"), "entry id")
    entry_root = pack_root / "entries" / entry_id
    source_root = entry_root / "source"
    entry_manifest = _read_json(entry_root / ENTRY_MANIFEST)
    if str(entry_manifest.get("id") or "") != entry_id:
        raise ValueError(f"Installed Online League entry manifest does not match: {entry_id}")
    payload = online_league_payload.assert_pack_payload_safe(source_root)
    folder_name = online_league_payload.validate_external_folder_name(entry.get("externalFolderName"))
    car_guid = str(entry.get("carGuid") or "").strip().lower()
    if payload.get("folderName") != "source":
        payload["folderName"] = folder_name
    if str(payload.get("carGuid") or "").strip().lower() != car_guid:
        raise ValueError(f"Installed Online League entry car_guid does not match its payload: {entry_id}")
    if str(entry_manifest.get("carGuid") or "").strip().lower() != car_guid:
        raise ValueError(f"Installed Online League entry manifest car_guid does not match: {entry_id}")
    if online_league_payload.validate_external_folder_name(entry_manifest.get("externalFolderName")) != folder_name:
        raise ValueError(f"Installed Online League entry folder name does not match: {entry_id}")
    fingerprint = online_league_deployments.payload_fingerprint(source_root)
    return source_root, payload, fingerprint


def _target_for(external_liveries_root: Path, folder_name: str) -> Path:
    target = external_liveries_root / online_league_payload.validate_external_folder_name(folder_name)
    try:
        target.resolve().relative_to(external_liveries_root.resolve())
    except Exception as exc:
        raise ValueError("Online League target is outside ExternalLiveries.") from exc
    return target


def _guid_folders(external_liveries_root: Path, car_guid: str, target: Path) -> list[str]:
    conflicts: list[str] = []
    if not external_liveries_root.is_dir():
        return conflicts
    for folder in sorted((item for item in external_liveries_root.iterdir() if item.is_dir()), key=lambda item: item.name.lower()):
        try:
            if folder.resolve() == target.resolve():
                continue
        except Exception:
            if str(folder).lower() == str(target).lower():
                continue
        try:
            inspection = online_league_payload.inspect_payload(folder)
        except Exception:
            continue
        if str(inspection.get("carGuid") or "").strip().lower() == car_guid:
            conflicts.append(str(folder))
    return conflicts


def activation_preflight(
    library_root: str | Path,
    installed_pack_id: Any,
    external_liveries_root: str | Path,
) -> dict[str, Any]:
    library = Path(library_root).expanduser()
    external_root = Path(external_liveries_root).expanduser()
    pack_root, manifest, entries = _load_pack(library, installed_pack_id)
    plans: list[dict[str, Any]] = []
    errors: list[str] = []
    warnings: list[str] = []
    for entry in entries:
        entry_id = str(entry.get("id") or "").strip()
        try:
            source_root, _, fingerprint = _entry_payload(pack_root, entry)
            folder_name = online_league_payload.validate_external_folder_name(entry.get("externalFolderName"))
            car_guid = str(fingerprint.get("carGuid") or "").strip().lower()
            target = _target_for(external_root, folder_name)
            source_id = online_league_deployments.source_descriptor_id(folder_name, car_guid)
            classification = online_league_deployments.classify_existing_target(
                target,
                expected_fingerprint=fingerprint.get("value"),
                library_root=library,
            )
            guid_conflicts = _guid_folders(external_root, car_guid, target)
            if guid_conflicts:
                classification = {
                    "status": "conflict",
                    "ownership": "",
                    "ready": False,
                    "reason": f"Another ExternalLiveries folder already uses car_guid {car_guid}: {guid_conflicts[0]}",
                }
            status = str(classification.get("status") or "conflict")
            ownership = str(classification.get("ownership") or "")
            action = "install" if status == "missing" else (
                "reference_league_owned" if status == "identical" and ownership == online_league_deployments.OWNERSHIP_LEAGUE
                else "reuse_existing" if status == "identical"
                else "conflict"
            )
            plan_errors = [] if classification.get("ready") else [str(classification.get("reason") or "ExternalLiveries target conflicts with this League livery.")]
            errors.extend(f"{entry_id}: {item}" for item in plan_errors)
            plans.append({
                "entryId": entry_id,
                "displayName": str(entry.get("displayName") or folder_name),
                "sourceRoot": str(source_root),
                "targetFolder": str(target),
                "externalFolderName": folder_name,
                "carGuid": car_guid,
                "sourceDescriptorId": source_id,
                "payloadFingerprint": fingerprint.get("value") or "",
                "action": action,
                "ownership": ownership,
                "ready": not plan_errors,
                "errors": plan_errors,
                "guidConflicts": guid_conflicts,
            })
        except Exception as exc:
            message = str(exc)
            errors.append(f"{entry_id or 'entry'}: {message}")
            plans.append({"entryId": entry_id, "action": "conflict", "ownership": "", "ready": False, "errors": [message]})
    counts = {
        "install": sum(1 for item in plans if item.get("action") == "install"),
        "reuseExisting": sum(1 for item in plans if item.get("action") == "reuse_existing"),
        "referenceLeagueOwned": sum(1 for item in plans if item.get("action") == "reference_league_owned"),
        "conflict": sum(1 for item in plans if item.get("action") == "conflict"),
    }
    return {
        "installedId": str(manifest.get("installedId") or installed_pack_id),
        "name": str(manifest.get("name") or installed_pack_id),
        "packVersion": str(manifest.get("packVersion") or ""),
        "workflowScope": workflow_scopes.SCOPE_OFFICIAL,
        "packRoot": str(pack_root),
        "externalLiveriesRoot": str(external_root),
        "ready": not errors,
        "errors": errors,
        "warnings": warnings,
        "plans": plans,
        "counts": counts,
    }


def _copy_payload(source_root: Path, target_root: Path) -> int:
    inspection = online_league_payload.assert_pack_payload_safe(source_root)
    allowed = [online_league_payload.EXTERNAL_LIVERY_JSON, *(str(item.get("path") or "") for item in inspection.get("referencedFiles") or [])]
    copied = 0
    for relative_text in allowed:
        relative = Path(*relative_text.replace("\\", "/").split("/"))
        package_zip.copy_file(source_root / relative, target_root / relative)
        copied += 1
    return copied


def _backup_session(library_root: Path, installed_pack_id: str, reason: str) -> Path:
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    safe_pack = re.sub(r"[^0-9A-Za-z._-]+", "_", installed_pack_id).strip("._-") or "league_pack"
    root = library_root / BACKUP_DIRNAME
    target = root / f"{stamp}__{safe_pack}__{reason}"
    index = 1
    while target.exists():
        index += 1
        target = root / f"{stamp}__{safe_pack}__{reason}_{index}"
    target.mkdir(parents=True, exist_ok=False)
    return target


def activate_pack(
    library_root: str | Path,
    installed_pack_id: Any,
    external_liveries_root: str | Path,
    *,
    confirm_token: str,
) -> dict[str, Any]:
    if str(confirm_token or "").strip() != ACTIVATE_CONFIRM_TOKEN:
        raise PermissionError("Online League activation requires explicit confirmation.")
    library = Path(library_root).expanduser()
    preflight = activation_preflight(library, installed_pack_id, external_liveries_root)
    if not preflight.get("ready"):
        raise RuntimeError("Online League activation preflight failed:\n" + "\n".join(str(item) for item in preflight.get("errors") or []))
    pack_root = Path(str(preflight.get("packRoot") or ""))
    manifest_path = pack_root / INSTALLED_MANIFEST
    manifest = _read_json(manifest_path)
    external_root = Path(str(preflight.get("externalLiveriesRoot") or "")).expanduser()
    external_root.mkdir(parents=True, exist_ok=True)
    registered: list[dict[str, Any]] = []
    installed_targets: list[Path] = []
    copied_count = 0
    rollback_backup: Path | None = None
    try:
        for plan in preflight.get("plans") or []:
            target = Path(str(plan.get("targetFolder") or ""))
            if plan.get("action") == "install":
                staging = external_root / f"llstage_{uuid.uuid4().hex[:12]}"
                try:
                    copied_count += _copy_payload(Path(str(plan.get("sourceRoot") or "")), staging)
                    staged_fingerprint = online_league_deployments.payload_fingerprint(staging)
                    if staged_fingerprint.get("value") != plan.get("payloadFingerprint"):
                        raise RuntimeError(f"League activation staging verification failed: {plan.get('entryId')}")
                    if target.exists():
                        raise FileExistsError(f"ExternalLiveries target appeared during activation: {target}")
                    staging.replace(target)
                    installed_targets.append(target)
                finally:
                    if staging.exists():
                        shutil.rmtree(staging, ignore_errors=True)
            ownership = (
                online_league_deployments.OWNERSHIP_REUSED
                if plan.get("action") == "reuse_existing"
                else online_league_deployments.OWNERSHIP_LEAGUE
            )
            registration = online_league_deployments.register_reference(
                library,
                installed_pack_id=preflight.get("installedId"),
                entry_id=plan.get("entryId"),
                external_folder_name=plan.get("externalFolderName"),
                car_guid=plan.get("carGuid"),
                payload_fingerprint_value=plan.get("payloadFingerprint"),
                ownership=ownership,
            )
            registered.append({
                "deployment": registration.get("deployment") or {},
                "referenceAdded": bool(registration.get("referenceAdded")),
                "entryId": plan.get("entryId"),
            })
        now = _now_iso()
        manifest["activationState"] = "active"
        manifest["activatedAt"] = now
        manifest["activationSummary"] = {
            **dict(preflight.get("counts") or {}),
            "entryCount": len(preflight.get("plans") or []),
            "copiedFileCount": copied_count,
        }
        _write_json_atomic(manifest_path, manifest)
    except Exception as exc:
        for registration in reversed(registered):
            if registration.get("referenceAdded"):
                try:
                    online_league_deployments.release_reference(
                        library,
                        installed_pack_id=preflight.get("installedId"),
                        entry_id=registration.get("entryId"),
                    )
                except Exception:
                    pass
        for target in installed_targets:
            if not target.exists():
                continue
            if rollback_backup is None:
                rollback_backup = _backup_session(library, str(preflight.get("installedId") or installed_pack_id), "failed_activation")
            destination = rollback_backup / "ExternalLiveries" / target.name
            destination.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(target), str(destination))
        backup_note = f" Newly installed files were preserved in: {rollback_backup}." if rollback_backup else ""
        raise RuntimeError(f"Online League activation failed.{backup_note} {exc}") from exc
    return {
        "activated": True,
        "installedId": preflight.get("installedId"),
        "activationState": "active",
        "preflight": preflight,
        "copiedFileCount": copied_count,
        "installedCount": int((preflight.get("counts") or {}).get("install") or 0),
        "reusedExistingCount": int((preflight.get("counts") or {}).get("reuseExisting") or 0),
        "referencedLeagueOwnedCount": int((preflight.get("counts") or {}).get("referenceLeagueOwned") or 0),
        "registryPath": str(library / online_league_deployments.REGISTRY_FILENAME),
    }


def deactivation_preflight(
    library_root: str | Path,
    installed_pack_id: Any,
    external_liveries_root: str | Path,
) -> dict[str, Any]:
    library = Path(library_root).expanduser()
    external_root = Path(external_liveries_root).expanduser()
    pack_root, manifest, entries = _load_pack(library, installed_pack_id)
    registry = online_league_deployments.load_registry(library)
    plans: list[dict[str, Any]] = []
    errors: list[str] = []
    pack_id = str(manifest.get("installedId") or installed_pack_id)
    for entry in entries:
        entry_id = str(entry.get("id") or "").strip()
        folder_name = online_league_payload.validate_external_folder_name(entry.get("externalFolderName"))
        car_guid = str(entry.get("carGuid") or "").strip().lower()
        source_id = online_league_deployments.source_descriptor_id(folder_name, car_guid)
        deployment = next((item for item in registry.get("deployments") or [] if item.get("sourceDescriptorId") == source_id), None)
        reference = next((item for item in (deployment.get("references") if deployment else []) or [] if str(item.get("installedPackId") or "").lower() == pack_id.lower() and str(item.get("entryId") or "").lower() == entry_id.lower()), None)
        target = _target_for(external_root, folder_name)
        if not deployment or not reference:
            plans.append({"entryId": entry_id, "action": "already_inactive", "ready": True, "targetFolder": str(target), "sourceDescriptorId": source_id})
            continue
        ownership = str(deployment.get("ownership") or "")
        reference_count = len(deployment.get("references") or [])
        action = "release_reused" if ownership == online_league_deployments.OWNERSHIP_REUSED else (
            "release_shared" if reference_count > 1 else "backup_and_release"
        )
        plan_errors: list[str] = []
        if action == "backup_and_release" and target.exists():
            try:
                fingerprint = online_league_deployments.payload_fingerprint(target)
                if fingerprint.get("value") != deployment.get("payloadFingerprint"):
                    plan_errors.append("League-owned ExternalLiveries payload was modified and will not be removed automatically.")
            except Exception as exc:
                plan_errors.append(f"League-owned ExternalLiveries payload could not be verified: {exc}")
        if plan_errors:
            action = "conflict"
            errors.extend(f"{entry_id}: {item}" for item in plan_errors)
        plans.append({
            "entryId": entry_id,
            "action": action,
            "ready": not plan_errors,
            "ownership": ownership,
            "referenceCount": reference_count,
            "targetFolder": str(target),
            "sourceDescriptorId": source_id,
            "externalFolderName": folder_name,
            "carGuid": car_guid,
            "payloadFingerprint": deployment.get("payloadFingerprint") or "",
            "targetExists": target.is_dir(),
            "errors": plan_errors,
        })
    return {
        "installedId": pack_id,
        "name": str(manifest.get("name") or installed_pack_id),
        "packRoot": str(pack_root),
        "externalLiveriesRoot": str(external_root),
        "ready": not errors,
        "errors": errors,
        "warnings": list(registry.get("warnings") or []),
        "plans": plans,
        "counts": {
            "backup": sum(1 for item in plans if item.get("action") == "backup_and_release" and item.get("targetExists")),
            "reusedExisting": sum(1 for item in plans if item.get("action") == "release_reused"),
            "shared": sum(1 for item in plans if item.get("action") == "release_shared"),
            "alreadyInactive": sum(1 for item in plans if item.get("action") == "already_inactive"),
            "conflict": sum(1 for item in plans if item.get("action") == "conflict"),
        },
    }


def deactivate_pack(
    library_root: str | Path,
    installed_pack_id: Any,
    external_liveries_root: str | Path,
    *,
    confirm_token: str,
) -> dict[str, Any]:
    if str(confirm_token or "").strip() != DEACTIVATE_CONFIRM_TOKEN:
        raise PermissionError("Online League deactivation requires explicit confirmation.")
    library = Path(library_root).expanduser()
    preflight = deactivation_preflight(library, installed_pack_id, external_liveries_root)
    if not preflight.get("ready"):
        raise RuntimeError("Online League deactivation preflight failed:\n" + "\n".join(str(item) for item in preflight.get("errors") or []))
    backup_root: Path | None = None
    moved: list[tuple[Path, Path]] = []
    released: list[dict[str, Any]] = []
    try:
        for plan in preflight.get("plans") or []:
            action = str(plan.get("action") or "")
            if action == "already_inactive":
                continue
            target = Path(str(plan.get("targetFolder") or ""))
            if action == "backup_and_release" and target.is_dir():
                if backup_root is None:
                    backup_root = _backup_session(library, str(preflight.get("installedId") or installed_pack_id), "deactivated")
                backup_target = backup_root / "ExternalLiveries" / target.name
                backup_target.parent.mkdir(parents=True, exist_ok=True)
                shutil.move(str(target), str(backup_target))
                moved.append((target, backup_target))
            release = online_league_deployments.release_reference(
                library,
                installed_pack_id=preflight.get("installedId"),
                entry_id=plan.get("entryId"),
            )
            if release.get("released"):
                released.append({"entryId": plan.get("entryId"), "deployment": release.get("deployment") or {}})
        pack_root = Path(str(preflight.get("packRoot") or ""))
        manifest_path = pack_root / INSTALLED_MANIFEST
        manifest = _read_json(manifest_path)
        manifest["activationState"] = "local_only"
        manifest["deactivatedAt"] = _now_iso()
        manifest["lastDeactivationBackup"] = str(backup_root) if backup_root else ""
        _write_json_atomic(manifest_path, manifest)
        if backup_root:
            _write_json_atomic(backup_root / "deactivation_manifest.json", {
                "action": "deactivate_online_league_pack",
                "createdAt": _now_iso(),
                "installedId": preflight.get("installedId"),
                "packName": preflight.get("name"),
                "moved": [{"source": str(source), "backup": str(backup)} for source, backup in moved],
            })
    except Exception as exc:
        rollback_errors: list[str] = []
        for source, backup in reversed(moved):
            try:
                if backup.exists() and not source.exists():
                    source.parent.mkdir(parents=True, exist_ok=True)
                    shutil.move(str(backup), str(source))
            except Exception as rollback_exc:
                rollback_errors.append(str(rollback_exc))
        for released_item in released:
            deployment = released_item.get("deployment") if isinstance(released_item.get("deployment"), dict) else {}
            try:
                online_league_deployments.register_reference(
                    library,
                    installed_pack_id=preflight.get("installedId"),
                    entry_id=released_item.get("entryId"),
                    external_folder_name=deployment.get("externalFolderName"),
                    car_guid=deployment.get("carGuid"),
                    payload_fingerprint_value=deployment.get("payloadFingerprint"),
                    ownership=deployment.get("ownership"),
                )
            except Exception as rollback_exc:
                rollback_errors.append(str(rollback_exc))
        detail = f"; rollback incomplete: {'; '.join(rollback_errors)}" if rollback_errors else ""
        raise RuntimeError(f"Online League deactivation failed{detail}: {exc}") from exc
    return {
        "deactivated": True,
        "installedId": preflight.get("installedId"),
        "activationState": "local_only",
        "preflight": preflight,
        "backupRoot": str(backup_root) if backup_root else "",
        "backedUpCount": len(moved),
        "releasedCount": len(released),
    }


def deletion_preflight(
    library_root: str | Path,
    installed_pack_id: Any,
    external_liveries_root: str | Path,
) -> dict[str, Any]:
    library = Path(library_root).expanduser()
    pack_root, manifest, entries = _load_pack(library, installed_pack_id)
    pack_id = str(manifest.get("installedId") or installed_pack_id)
    deactivation = deactivation_preflight(library, pack_id, external_liveries_root)
    registry = online_league_deployments.load_registry(library)
    errors = [str(item) for item in deactivation.get("errors") or [] if str(item).strip()]
    warnings = [str(item) for item in registry.get("warnings") or [] if str(item).strip()]
    if warnings:
        errors.append("League deployment registry contains invalid entries and must be resolved before deleting a pack.")

    known_entry_ids = {str(entry.get("id") or "").strip().lower() for entry in entries}
    orphan_references: list[str] = []
    for deployment in registry.get("deployments") or []:
        for reference in deployment.get("references") or []:
            if str(reference.get("installedPackId") or "").strip().lower() != pack_id.lower():
                continue
            entry_id = str(reference.get("entryId") or "").strip()
            if entry_id.lower() not in known_entry_ids:
                orphan_references.append(entry_id or "unknown")
    if orphan_references:
        errors.append(f"League deployment registry contains an unknown reference for this pack: {orphan_references[0]}")

    plans: list[dict[str, Any]] = []
    for raw in deactivation.get("plans") or []:
        plan = dict(raw)
        current_action = str(plan.get("action") or "conflict")
        if current_action == "backup_and_release":
            action = "delete_league_owned" if plan.get("targetExists") else "release_missing"
        elif current_action == "release_reused":
            action = "keep_existing"
        elif current_action == "release_shared":
            action = "keep_shared"
        elif current_action == "already_inactive":
            action = "already_inactive"
        else:
            action = "conflict"
        plan["action"] = action
        plans.append(plan)

    counts = {
        "deleteLeagueOwned": sum(1 for item in plans if item.get("action") == "delete_league_owned"),
        "keepExisting": sum(1 for item in plans if item.get("action") == "keep_existing"),
        "keepShared": sum(1 for item in plans if item.get("action") == "keep_shared"),
        "releaseMissing": sum(1 for item in plans if item.get("action") == "release_missing"),
        "alreadyInactive": sum(1 for item in plans if item.get("action") == "already_inactive"),
        "conflict": sum(1 for item in plans if item.get("action") == "conflict"),
    }
    return {
        "installedId": pack_id,
        "name": str(manifest.get("name") or installed_pack_id),
        "packRoot": str(pack_root),
        "externalLiveriesRoot": str(deactivation.get("externalLiveriesRoot") or ""),
        "ready": not errors and not counts["conflict"],
        "errors": errors,
        "warnings": warnings,
        "plans": plans,
        "counts": counts,
    }


def _remove_tree_permanently(target: Path, allowed_root: Path) -> None:
    try:
        target.resolve().relative_to(allowed_root.resolve())
    except Exception as exc:
        raise ValueError("Online League delete transaction is outside its managed folder.") from exc

    def remove_readonly(function: Any, path: str, _error: Any) -> None:
        os.chmod(path, stat.S_IWRITE)
        function(path)

    shutil.rmtree(target, onerror=remove_readonly)


def _restore_registry_snapshot(registry_path: Path, snapshot_path: Path, existed: bool) -> None:
    if existed:
        if not snapshot_path.is_file():
            raise FileNotFoundError("Online League delete transaction registry snapshot is missing.")
        temp = registry_path.with_suffix(registry_path.suffix + ".restore.tmp")
        shutil.copy2(snapshot_path, temp)
        temp.replace(registry_path)
    elif registry_path.exists():
        registry_path.unlink()


def delete_pack(
    library_root: str | Path,
    installed_pack_id: Any,
    external_liveries_root: str | Path,
    *,
    confirm_token: str,
) -> dict[str, Any]:
    if str(confirm_token or "").strip() != DELETE_CONFIRM_TOKEN:
        raise PermissionError("Online League pack deletion requires explicit confirmation.")
    library = Path(library_root).expanduser()
    preflight = deletion_preflight(library, installed_pack_id, external_liveries_root)
    if not preflight.get("ready"):
        raise RuntimeError("Online League pack deletion preflight failed:\n" + "\n".join(str(item) for item in preflight.get("errors") or []))

    pack_root = Path(str(preflight.get("packRoot") or ""))
    transaction_base = library / DELETE_TRANSACTION_DIRNAME
    transaction_base.mkdir(parents=True, exist_ok=True)
    safe_pack = re.sub(r"[^0-9A-Za-z._-]+", "_", str(preflight.get("installedId") or installed_pack_id)).strip("._-") or "league_pack"
    transaction_root = transaction_base / f"{safe_pack}_{uuid.uuid4().hex}"
    transaction_root.mkdir(parents=False, exist_ok=False)
    external_stage = transaction_root / "ExternalLiveries"
    pack_stage = transaction_root / "InstalledPack"
    registry_path = library / online_league_deployments.REGISTRY_FILENAME
    registry_snapshot = transaction_root / "league_deployments.before.json"
    registry_existed = registry_path.is_file()
    if registry_existed:
        shutil.copy2(registry_path, registry_snapshot)

    moved_targets: list[tuple[Path, Path]] = []
    pack_moved = False
    try:
        for plan in preflight.get("plans") or []:
            if plan.get("action") != "delete_league_owned":
                continue
            target = Path(str(plan.get("targetFolder") or ""))
            if not target.is_dir():
                raise FileNotFoundError(f"League-owned ExternalLiveries target disappeared before deletion: {target}")
            fingerprint = online_league_deployments.payload_fingerprint(target)
            if str(fingerprint.get("value") or "") != str(plan.get("payloadFingerprint") or ""):
                raise RuntimeError(f"League-owned livery changed after preflight and was not deleted: {target.name}")
            staged = external_stage / target.name
            staged.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(target), str(staged))
            moved_targets.append((target, staged))

        for plan in preflight.get("plans") or []:
            if plan.get("action") == "already_inactive":
                continue
            online_league_deployments.release_reference(
                library,
                installed_pack_id=preflight.get("installedId"),
                entry_id=plan.get("entryId"),
            )

        shutil.move(str(pack_root), str(pack_stage))
        pack_moved = True
    except Exception as exc:
        rollback_errors: list[str] = []
        if pack_moved and pack_stage.exists() and not pack_root.exists():
            try:
                shutil.move(str(pack_stage), str(pack_root))
            except Exception as rollback_exc:
                rollback_errors.append(str(rollback_exc))
        for target, staged in reversed(moved_targets):
            try:
                if staged.exists() and not target.exists():
                    target.parent.mkdir(parents=True, exist_ok=True)
                    shutil.move(str(staged), str(target))
            except Exception as rollback_exc:
                rollback_errors.append(str(rollback_exc))
        try:
            _restore_registry_snapshot(registry_path, registry_snapshot, registry_existed)
        except Exception as rollback_exc:
            rollback_errors.append(str(rollback_exc))
        if not rollback_errors:
            _remove_tree_permanently(transaction_root, transaction_base)
            try:
                transaction_base.rmdir()
            except OSError:
                pass
        detail = f" Rollback incomplete: {'; '.join(rollback_errors)}" if rollback_errors else ""
        raise RuntimeError(f"Online League pack deletion failed.{detail} {exc}") from exc

    _remove_tree_permanently(transaction_root, transaction_base)
    try:
        transaction_base.rmdir()
    except OSError:
        pass
    counts = dict(preflight.get("counts") or {})
    return {
        "deleted": True,
        "installedId": preflight.get("installedId"),
        "name": preflight.get("name"),
        "permanentlyDeletedCount": int(counts.get("deleteLeagueOwned") or 0),
        "keptExistingCount": int(counts.get("keepExisting") or 0),
        "keptSharedCount": int(counts.get("keepShared") or 0),
        "preflight": preflight,
    }
