from __future__ import annotations

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

from liverylab_core import (
    official_saved_car_state,
    official_external_liveries,
    official_external_workflow,
    project_capabilities,
    project_types,
    saved_games_paths,
)


def normalize_guid(value: Any) -> str:
    return official_saved_car_state.normalize_guid(value)


def _modified_iso(path: Path) -> str:
    try:
        return datetime.fromtimestamp(path.stat().st_mtime).isoformat(timespec="seconds")
    except Exception:
        return ""


def _car_model_from_saved_car_name(path: Path, guid: str) -> str:
    return official_saved_car_state.car_model_from_filename(path)


def _inspect_saved_car_file(path: Path) -> dict[str, Any]:
    if not path.exists() or not path.is_file():
        return {}
    return official_saved_car_state.inspect_saved_car_file(path)


def saved_car_from_path(path: str | Path, *, root: str | Path | None = None) -> dict[str, Any]:
    item = Path(path).expanduser()
    guid = normalize_guid(item.name)
    root_path = Path(root).expanduser() if root else item.parent
    inspection = _inspect_saved_car_file(item)
    instance_car_guid = normalize_guid(inspection.get("carGuid"))
    livery_linker_guid = normalize_guid(inspection.get("liveryLinkerGuid"))
    car_guid = official_saved_car_state.official_livery_guid(inspection) or guid
    car_model = str(inspection.get("carModel") or "") or _car_model_from_saved_car_name(item, guid)
    row = {
        "guid": car_guid,
        "carGuid": car_guid,
        "instanceCarGuid": instance_car_guid,
        "liveryLinkerGuid": livery_linker_guid,
        "liveryLinkerPresent": bool(inspection.get("liveryLinkerPresent")),
        "filenameGuid": normalize_guid(inspection.get("filenameGuid")) or guid,
        "carModel": car_model,
        "filenameCarModel": str(inspection.get("filenameCarModel") or "") or _car_model_from_saved_car_name(item, guid),
        "filename": item.name,
        "path": str(item),
        "root": str(root_path),
        "exists": item.exists() and item.is_file(),
        "modifiedAt": _modified_iso(item),
        "size": item.stat().st_size if item.exists() and item.is_file() else 0,
        "savedCarStateValid": bool(inspection.get("valid")) if inspection else False,
        "savedCarStateWrapped": bool(inspection.get("wrapped")) if inspection else False,
        "savedCarStateFieldPath": str(inspection.get("fieldPath") or "") if inspection else "",
        "savedCarWarnings": list(inspection.get("warnings") or []) if inspection else [],
        "savedCarErrors": list(inspection.get("errors") or []) if inspection else [],
    }
    if inspection:
        row.update(
            {
                "carData": inspection.get("carData") or "",
                "actorPath": inspection.get("actorPath") or "",
                "mechanicalPreset": inspection.get("mechanicalPreset") or "",
                "visualPreset": inspection.get("visualPreset") or "",
                "compatibleRim": inspection.get("compatibleRim") or "",
                "rimDesign": inspection.get("rimDesign") or "",
                "rimGuid": inspection.get("rimGuid") or "",
                "materialRefs": list(inspection.get("materialRefs") or []),
                "compatibleParts": list(inspection.get("compatibleParts") or []),
                "contentRefs": list(inspection.get("contentRefs") or []),
                "savedCarGuids": list(inspection.get("guids") or []),
            }
        )
    return row


def _row_link_guids(row: dict[str, Any]) -> list[str]:
    candidates = [
        row.get("carGuid"),
        row.get("guid"),
        row.get("instanceCarGuid"),
        row.get("liveryLinkerGuid"),
        row.get("filenameGuid"),
    ]
    candidates.extend(row.get("savedCarGuids") or [])
    out: list[str] = []
    seen: set[str] = set()
    for candidate in candidates:
        guid = normalize_guid(candidate)
        if not guid or guid in seen:
            continue
        seen.add(guid)
        out.append(guid)
    return out


def _external_livery_guid_links(saved_games_ace_root: str | Path | None = None) -> dict[str, list[dict[str, Any]]]:
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    links: dict[str, list[dict[str, Any]]] = {}
    if not external_root.exists() or not external_root.is_dir():
        return links
    for json_path in sorted(external_root.glob(f"*/{official_external_liveries.EXTERNAL_LIVERY_JSON}"), key=lambda item: str(item).lower()):
        try:
            data = json.loads(json_path.read_text(encoding="utf-8"))
        except Exception:
            continue
        if not isinstance(data, dict):
            continue
        guid = normalize_guid(data.get("car_guid"))
        if not guid:
            continue
        folder = json_path.parent
        links.setdefault(guid, []).append(
            {
                "source": "external_livery_json",
                "folderName": folder.name,
                "folder": str(folder),
                "externalLiveryJson": str(json_path),
            }
        )
    return links


def _project_guid_links(project_entries: list[dict[str, Any]] | None = None) -> dict[str, list[dict[str, Any]]]:
    links: dict[str, list[dict[str, Any]]] = {}
    for entry in project_entries or []:
        if not isinstance(entry, dict):
            continue
        if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            continue
        role = official_external_workflow.role_for_entry(entry)
        guid = normalize_guid(entry.get("car_guid") or entry.get("carGuid"))
        details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
        descriptor = details.get("descriptor") if isinstance(details.get("descriptor"), dict) else {}
        guid = guid or normalize_guid(details.get("linkedSavedCarGuid") or descriptor.get("carGuid"))
        if not guid:
            continue
        links.setdefault(guid, []).append(
            {
                "source": "liverylab_project" if role == official_external_workflow.ROLE_PROJECT else "liverylab_library",
                "key": entry.get("key") or "",
                "displayName": entry.get("display_name") or entry.get("skin_id") or "",
                "role": role,
                "localRoot": entry.get("local_root") or "",
            }
        )
    return links


def _merge_guid_links(*sources: dict[str, list[dict[str, Any]]]) -> dict[str, list[dict[str, Any]]]:
    merged: dict[str, list[dict[str, Any]]] = {}
    for source in sources:
        for guid, rows in source.items():
            merged.setdefault(guid, []).extend(rows)
    return merged


def scan_saved_cars(
    saved_games_ace_root: str | Path | None = None,
    *,
    project_entries: list[dict[str, Any]] | None = None,
    custom_only: bool = True,
) -> dict[str, Any]:
    roots = project_capabilities.saved_cars_roots(saved_games_ace_root)
    guid_links = _merge_guid_links(
        _external_livery_guid_links(saved_games_ace_root),
        _project_guid_links(project_entries),
    )
    items: list[dict[str, Any]] = []
    errors: list[str] = []
    total_count = 0
    seen_paths: set[str] = set()

    for root in roots:
        try:
            candidates = sorted(root.glob(f"*{official_external_liveries.SAVED_CAR_SUFFIX}"), key=lambda item: str(item).lower())
        except Exception as exc:
            errors.append(f"{root}: {exc}")
            continue
        for path in candidates:
            path_key = str(path).lower()
            if path_key in seen_paths:
                continue
            seen_paths.add(path_key)
            total_count += 1
            row = saved_car_from_path(path, root=root)
            links: list[dict[str, Any]] = []
            for guid in _row_link_guids(row):
                links.extend(guid_links.get(guid, []))
            row["customLivery"] = bool(links)
            row["markers"] = links
            row["markerSources"] = sorted({str(link.get("source") or "") for link in links if str(link.get("source") or "")})
            row["linkedExternalLiveryCount"] = sum(1 for link in links if link.get("source") == "external_livery_json")
            row["linkedLiveryLabProjectCount"] = sum(1 for link in links if str(link.get("source") or "").startswith("liverylab_"))
            if custom_only and not row["customLivery"]:
                continue
            items.append(row)

    items.sort(key=lambda row: (str(row.get("carModel") or "").lower(), str(row.get("filename") or "").lower()))
    return {
        "savedGamesAceRoot": str(saved_games_paths.ace_root(saved_games_ace_root)),
        "roots": [str(root) for root in roots],
        "customOnly": bool(custom_only),
        "items": items,
        "itemCount": len(items),
        "totalSavedCarCount": total_count,
        "hiddenGarageCount": max(0, total_count - len(items)) if custom_only else 0,
        "linkedGuidCount": len(guid_links),
        "errors": errors,
    }
