from __future__ import annotations

import re
from pathlib import Path
from typing import Any

from liverylab_core import project_types


ROLE_LIBRARY = "library"
ROLE_PROJECT = "project"
ROLE_DEPLOYMENT = "deployment"

STORAGE_ROLE_LIVERY_LIBRARY = "livery_library"
STORAGE_ROLE_LIVERY_PROJECTS = "official_livery_projects"
LEGACY_STORAGE_ROLE_LIVERY_PROJECTS = "livery_projects"
STORAGE_ROLE_SAVED_GAMES = "saved_games"

DEPLOYMENT_TARGET_SAVED_GAMES_EXTERNAL_LIVERIES = "saved_games_external_liveries"

ROLE_LABELS = {
    ROLE_LIBRARY: "Installed library copy",
    ROLE_PROJECT: "Editable project",
    ROLE_DEPLOYMENT: "Saved Games deployment",
}

ROLE_ALIASES = {
    "installed": ROLE_LIBRARY,
    "installed_library": ROLE_LIBRARY,
    "library": ROLE_LIBRARY,
    "library_copy": ROLE_LIBRARY,
    "local_library": ROLE_LIBRARY,
    "my_liveries": ROLE_LIBRARY,
    "editable": ROLE_PROJECT,
    "editable_project": ROLE_PROJECT,
    "official_livery_projects": ROLE_PROJECT,
    "livery_project": ROLE_PROJECT,
    "livery_projects": ROLE_PROJECT,
    "project": ROLE_PROJECT,
    "activate": ROLE_DEPLOYMENT,
    "activated": ROLE_DEPLOYMENT,
    "deployed": ROLE_DEPLOYMENT,
    "deployment": ROLE_DEPLOYMENT,
    "game": ROLE_DEPLOYMENT,
    "saved_games": ROLE_DEPLOYMENT,
    "saved_games_external_liveries": ROLE_DEPLOYMENT,
}

GUID_PATTERN = re.compile(
    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
    re.IGNORECASE,
)


def normalize_role(value: Any, default: str = ROLE_LIBRARY) -> str:
    raw = str(value or "").strip().lower().replace("-", "_").replace(" ", "_")
    if raw in {ROLE_LIBRARY, ROLE_PROJECT, ROLE_DEPLOYMENT}:
        return raw
    if raw in ROLE_ALIASES:
        return ROLE_ALIASES[raw]
    return default


def is_official_external_type(entry: dict[str, Any] | None) -> bool:
    data = entry if isinstance(entry, dict) else {}
    return project_types.project_type_for_entry(data) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY


def role_for_entry(entry: dict[str, Any] | None, default: str = ROLE_LIBRARY) -> str:
    data = entry if isinstance(entry, dict) else {}
    details = data.get("official_external") if isinstance(data.get("official_external"), dict) else {}
    raw = (
        details.get("role")
        or data.get("official_external_role")
        or data.get("officialExternalRole")
        or data.get("storage_role")
        or data.get("storageRole")
    )
    if raw:
        return normalize_role(raw, default=default)
    if not is_official_external_type(data):
        return default
    state = str(data.get("project_state") or data.get("projectState") or "").strip().lower()
    origin = str(data.get("origin") or data.get("skin_origin") or "").strip().lower()
    if state == "editable" or origin == "created":
        return ROLE_PROJECT
    return default


def role_label(role: str) -> str:
    return ROLE_LABELS.get(normalize_role(role), "Official external")


def library_entry_metadata() -> dict[str, Any]:
    return {
        "official_external_role": ROLE_LIBRARY,
        "storage_role": STORAGE_ROLE_LIVERY_LIBRARY,
        "deployment_target": DEPLOYMENT_TARGET_SAVED_GAMES_EXTERNAL_LIVERIES,
        "editable_project": False,
    }


def apply_library_entry_metadata(entry: dict[str, Any]) -> dict[str, Any]:
    next_entry = dict(entry)
    next_entry.update(library_entry_metadata())
    details = next_entry.get("official_external") if isinstance(next_entry.get("official_external"), dict) else {}
    next_entry["official_external"] = {
        **details,
        "role": ROLE_LIBRARY,
        "roleLabel": role_label(ROLE_LIBRARY),
        "storageRole": STORAGE_ROLE_LIVERY_LIBRARY,
        "deploymentTarget": DEPLOYMENT_TARGET_SAVED_GAMES_EXTERNAL_LIVERIES,
    }
    return next_entry


def external_descriptor_id(folder_name: Any, car_guid: Any) -> str:
    folder = str(folder_name or "").strip().lower()
    guid = str(car_guid or "").strip().lower().strip("{}")
    if not folder or not GUID_PATTERN.fullmatch(guid):
        return ""
    return f"official_external_livery:{folder}:{guid}"


def source_descriptor_id_for_library_entry(entry: dict[str, Any] | None) -> str:
    data = entry if isinstance(entry, dict) else {}
    if not is_official_external_type(data) or role_for_entry(data) != ROLE_LIBRARY:
        return ""
    details = data.get("official_external") if isinstance(data.get("official_external"), dict) else {}
    explicit = str(details.get("sourceDescriptorId") or "").strip().lower()
    if explicit:
        return explicit
    target_folder = str(details.get("deploymentTargetFolder") or "").strip()
    folder_name = Path(target_folder).name if target_folder else str(
        details.get("externalFolderName")
        or details.get("sourceFolderName")
        or details.get("plannedExternalFolder")
        or ""
    ).strip()
    descriptor_id = external_descriptor_id(
        folder_name,
        data.get("car_guid")
        or details.get("linkedSavedCarGuid")
        or details.get("carGuid"),
    )
    if descriptor_id:
        return descriptor_id
    return str(data.get("key") or "").strip().lower()


def is_managed_package_library_entry(entry: dict[str, Any] | None) -> bool:
    data = entry if isinstance(entry, dict) else {}
    return bool(
        is_official_external_type(data)
        and role_for_entry(data) == ROLE_LIBRARY
        and str(data.get("origin") or data.get("skin_origin") or "").strip().lower() == "package_import"
    )


def trusted_car_model_for_entry(entry: dict[str, Any] | None, fallback: Any = "") -> str:
    data = entry if isinstance(entry, dict) else {}
    details = data.get("official_external") if isinstance(data.get("official_external"), dict) else {}
    generated = details.get("generatedSavedCar") if isinstance(details.get("generatedSavedCar"), dict) else {}
    migration = details.get("legacyMigration") if isinstance(details.get("legacyMigration"), dict) else {}
    if is_managed_package_library_entry(data):
        candidates = (
            generated.get("carModel"),
            migration.get("sourceCarModel"),
            data.get("car_model"),
            fallback,
        )
    else:
        candidates = (
            data.get("car_model"),
            generated.get("carModel"),
            fallback,
        )
    for candidate in candidates:
        value = str(candidate or "").strip()
        if value:
            return value
    return ""


def car_model_for_library_sync(entry: dict[str, Any] | None, descriptor_car_model: Any) -> str:
    descriptor_value = str(descriptor_car_model or "").strip()
    if not is_managed_package_library_entry(entry):
        return descriptor_value
    return trusted_car_model_for_entry(entry, fallback=descriptor_value)


def prune_managed_package_external_shadows(
    entries: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    owners: dict[str, dict[str, Any]] = {}
    for item in entries:
        if not isinstance(item, dict) or not is_managed_package_library_entry(item):
            continue
        source_id = source_descriptor_id_for_library_entry(item)
        if source_id:
            owners.setdefault(source_id, item)
    if not owners:
        return list(entries), []

    kept: list[dict[str, Any]] = []
    pruned: list[dict[str, Any]] = []
    for item in entries:
        if not isinstance(item, dict):
            kept.append(item)
            continue
        source_id = source_descriptor_id_for_library_entry(item)
        owner = owners.get(source_id)
        item_key = str(item.get("key") or "").strip().lower()
        item_origin = str(item.get("origin") or item.get("skin_origin") or "").strip().lower()
        auto_discovered_shadow = bool(
            owner is not None
            and item is not owner
            and item_origin == "official_external"
            and item_key == source_id
        )
        if not auto_discovered_shadow:
            kept.append(item)
            continue
        pruned.append({
            "key": item.get("key") or source_id,
            "sourceDescriptorId": source_id,
            "reason": "managed_package_deployment_shadow",
            "ownerKey": owner.get("key") or "",
            "localRoot": item.get("local_root") or "",
            "ownerLocalRoot": owner.get("local_root") or "",
        })
    return kept, pruned
