from __future__ import annotations

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


PROJECT_ROOT_TOKEN = "${LIVERYLAB_PROJECT_ROOT}"

_MACHINE_PATH_PATTERN = re.compile(
    r"(?:[a-z]:[\\/]|\\\\(?:\?\\)?[^\\/]|file:///[a-z]:/)",
    re.IGNORECASE,
)

_OFFICIAL_RUNTIME_KEYS = {
    "assetstatus",
    "deactivatedat",
    "deployed",
    "deployedat",
    "deployedpath",
    "deployment",
    "deploymentpreflight",
    "deploymentresult",
    "deploymentstatus",
    "deploymenttargetfolder",
    "descriptor",
    "disabledfrom",
    "disabledroot",
    "disabledto",
    "externaldiscoverystatus",
    "guidconflictcleanup",
    "lastdeactivation",
    "lastdeployedpath",
    "lastdeployment",
    "localmanifest",
    "localroot",
    "manifestpath",
    "projectfolder",
    "rawroot",
    "savedcararchivestatus",
    "savedcardeploymentstatus",
    "savedcarfound",
    "savedcarpaths",
    "savedcarsrestore",
    "savedcarsrestoredat",
    "savedcarvaultrestore",
    "sourcefolder",
    "sourcejson",
    "sourcemanifest",
    "validation",
    "vaultsession",
}

_DROP = object()


def contains_machine_path(value: Any) -> bool:
    if isinstance(value, dict):
        return any(contains_machine_path(key) or contains_machine_path(item) for key, item in value.items())
    if isinstance(value, (list, tuple, set)):
        return any(contains_machine_path(item) for item in value)
    return bool(_MACHINE_PATH_PATTERN.search(str(value or "")))


def _portable_value(value: Any, *, drop_runtime_keys: bool) -> Any:
    if isinstance(value, Path):
        value = str(value)
    if isinstance(value, dict):
        cleaned: dict[str, Any] = {}
        for raw_key, raw_value in value.items():
            key = str(raw_key)
            if drop_runtime_keys and key.replace("_", "").casefold() in _OFFICIAL_RUNTIME_KEYS:
                continue
            next_value = _portable_value(raw_value, drop_runtime_keys=drop_runtime_keys)
            if next_value is not _DROP:
                cleaned[key] = next_value
        return cleaned
    if isinstance(value, (list, tuple, set)):
        cleaned_items: list[Any] = []
        for item in value:
            next_value = _portable_value(item, drop_runtime_keys=drop_runtime_keys)
            if next_value is not _DROP:
                cleaned_items.append(next_value)
        return cleaned_items
    if isinstance(value, str) and contains_machine_path(value):
        return _DROP
    return value


def sanitize_official_metadata(value: Any) -> dict[str, Any]:
    if not isinstance(value, dict):
        return {}
    cleaned = _portable_value(value, drop_runtime_keys=True)
    return cleaned if isinstance(cleaned, dict) else {}


def _source_variants(source_root: str | Path) -> list[str]:
    source = str(Path(source_root).expanduser())
    values = {
        source,
        source.replace("\\", "/"),
        source.replace("/", "\\"),
    }
    return sorted((value for value in values if value), key=len, reverse=True)


def _tokenize_value(value: Any, source_variants: list[str]) -> Any:
    if isinstance(value, dict):
        cleaned: dict[str, Any] = {}
        for raw_key, raw_value in value.items():
            next_value = _tokenize_value(raw_value, source_variants)
            if next_value is not _DROP:
                cleaned[str(raw_key)] = next_value
        return cleaned
    if isinstance(value, list):
        cleaned_items: list[Any] = []
        for item in value:
            next_value = _tokenize_value(item, source_variants)
            if next_value is not _DROP:
                cleaned_items.append(next_value)
        return cleaned_items
    if not isinstance(value, str):
        return value
    text = value
    for source in source_variants:
        text = text.replace(source, PROJECT_ROOT_TOKEN)
    return _DROP if contains_machine_path(text) else text


def sanitize_exported_project_json_tree(
    project_root: str | Path,
    source_root: str | Path,
) -> dict[str, Any]:
    root = Path(project_root).expanduser()
    source_variants = _source_variants(source_root)
    changed: list[str] = []
    skipped: list[str] = []
    for path in sorted(root.rglob("*.json")):
        if not path.is_file():
            continue
        try:
            original = json.loads(path.read_text(encoding="utf-8-sig"))
        except Exception:
            skipped.append(path.relative_to(root).as_posix())
            continue
        cleaned = _tokenize_value(original, source_variants)
        if cleaned is _DROP:
            cleaned = {}
        if cleaned == original:
            continue
        path.write_text(json.dumps(cleaned, indent=2, ensure_ascii=False), encoding="utf-8")
        changed.append(path.relative_to(root).as_posix())
    return {
        "projectRoot": str(root),
        "sourceToken": PROJECT_ROOT_TOKEN,
        "changed": changed,
        "changedCount": len(changed),
        "skipped": skipped,
        "skippedCount": len(skipped),
    }


def machine_paths_in_json_tree(root: str | Path) -> list[dict[str, str]]:
    package_root = Path(root).expanduser()
    findings: list[dict[str, str]] = []

    def inspect(value: Any, json_path: str, file_path: Path) -> None:
        if isinstance(value, dict):
            for key, item in value.items():
                inspect(item, f"{json_path}.{key}" if json_path else str(key), file_path)
            return
        if isinstance(value, list):
            for index, item in enumerate(value):
                inspect(item, f"{json_path}[{index}]", file_path)
            return
        if isinstance(value, str) and contains_machine_path(value):
            findings.append({
                "file": file_path.relative_to(package_root).as_posix(),
                "jsonPath": json_path,
                "value": value,
            })

    for path in sorted(package_root.rglob("*.json")):
        if not path.is_file():
            continue
        try:
            data = json.loads(path.read_text(encoding="utf-8-sig"))
        except Exception:
            continue
        inspect(data, "", path)
    return findings
