from __future__ import annotations

from pathlib import Path
from typing import Any

from liverylab_core import official_external_liveries, official_external_project_assets, official_external_workflow, project_types


RUNTIME_TEXTURE_SUFFIXES = (".texture", ".texturemips")
HEALTH_SEVERITY_ORDER = {"error": 0, "warning": 1, "info": 2, "ok": 3}


def _health_row(
    key: str,
    severity: str,
    title: str,
    message: str,
    fix: str = "",
    details: dict[str, Any] | None = None,
) -> dict[str, Any]:
    return {
        "key": key,
        "severity": severity if severity in HEALTH_SEVERITY_ORDER else "info",
        "title": title,
        "message": message,
        "fix": fix,
        "details": details or {},
    }


def _health_result(rows: list[dict[str, Any]], *, ok_summary: str, warning_summary: str, error_summary: str) -> dict[str, Any]:
    counts = {"error": 0, "warning": 0, "info": 0, "ok": 0}
    for row in rows:
        severity = str(row.get("severity") or "info")
        counts[severity] = counts.get(severity, 0) + 1
    status = "ready"
    if counts["error"]:
        status = "blocked"
    elif counts["warning"]:
        status = "warning"
    return {
        "status": status,
        "ready": counts["error"] == 0,
        "summary": error_summary if counts["error"] else (warning_summary if counts["warning"] else ok_summary),
        "counts": counts,
        "rows": sorted(rows, key=lambda row: HEALTH_SEVERITY_ORDER.get(row.get("severity"), 2)),
    }


def local_validation_from_inspection(entry: dict[str, Any], inspection: dict[str, Any]) -> dict[str, Any]:
    data = entry if isinstance(entry, dict) else {}
    role = official_external_workflow.role_for_entry(data)
    rows: list[dict[str, Any]] = []
    local_root = str(inspection.get("localRoot") or data.get("local_root") or "").strip()

    rows.append(_health_row(
        "local_root",
        "ok" if local_root and not inspection.get("reason") else "error",
        "Local Official folder",
        f"Local Official folder is available: {local_root}" if local_root and not inspection.get("reason") else str(inspection.get("reason") or "Local Official folder is missing."),
        "Restore this livery from Official trash or recreate/import the local Official copy.",
        {"path": local_root},
    ))

    valid = inspection.get("valid") is True
    errors = [str(row) for row in (inspection.get("errors") if isinstance(inspection.get("errors"), list) else []) if str(row).strip()]
    warnings = [str(row) for row in (inspection.get("warnings") if isinstance(inspection.get("warnings"), list) else []) if str(row).strip()]
    external_json = str(inspection.get("externalLiveryJson") or "").strip()
    rows.append(_health_row(
        "external_livery_json",
        "ok" if valid else "error",
        "external_livery.json",
        "external_livery.json exists and parses." if valid else (errors[0] if errors else "external_livery.json is invalid or missing."),
        "Restore or refresh external_livery.json in the local Official folder.",
        {"path": external_json, "errors": errors},
    ))

    car_guid = str(inspection.get("carGuid") or data.get("car_guid") or "").strip()
    car_guid_valid = inspection.get("carGuidValid") is True
    rows.append(_health_row(
        "car_guid",
        "ok" if car_guid and car_guid_valid else ("warning" if car_guid else "error"),
        "car_guid",
        f"car_guid is present: {car_guid}" if car_guid and car_guid_valid else ("car_guid is present but does not look like a GUID." if car_guid else "car_guid is missing."),
        "Link or refresh the Official project metadata so external_livery.json has a valid car_guid.",
        {"carGuid": car_guid},
    ))

    material_batches = inspection.get("materialBatchNames") if isinstance(inspection.get("materialBatchNames"), list) else []
    materials = inspection.get("materials") if isinstance(inspection.get("materials"), list) else []
    rows.append(_health_row(
        "materials",
        "ok" if material_batches else "error",
        "Materials",
        f"{len(material_batches)} material batch(es): {', '.join(str(row) for row in material_batches)}" if material_batches else "No material batch names were found.",
        "Add at least one material with a batch_name, for example EXT_SKIN.",
        {"materialBatchNames": material_batches, "materialCount": len(materials)},
    ))

    texture_slots = inspection.get("textureSlots") if isinstance(inspection.get("textureSlots"), list) else []
    referenced_slots = [row for row in texture_slots if isinstance(row, dict) and row.get("referenced")]
    missing_refs = int(inspection.get("missingTextureReferenceCount") or 0)
    base_color = next((row for row in texture_slots if isinstance(row, dict) and row.get("slot") == "base_color" and row.get("exists")), None)
    rows.append(_health_row(
        "texture_references",
        "error" if missing_refs else ("ok" if referenced_slots else "warning"),
        "Texture references",
        f"{len(referenced_slots)} referenced texture(s), all present." if referenced_slots and not missing_refs else (f"{missing_refs} referenced texture file(s) are missing." if missing_refs else "No referenced PNG texture paths were found."),
        "Place referenced PNG files next to external_livery.json or update the texture paths.",
        {"referencedTextureCount": len(referenced_slots), "missingReferenceCount": missing_refs, "slots": texture_slots},
    ))
    rows.append(_health_row(
        "base_color",
        "ok" if base_color else "warning",
        "Base color PNG",
        f"Base color PNG found: {base_color.get('path')}" if base_color else "No existing base color PNG slot was detected.",
        "For the simple Official workflow, reference an EXT_Skin_C-style PNG from external_livery.json.",
        {"slot": base_color or {}},
    ))

    generated_count = int(inspection.get("generatedTextureCount") or 0) + int(inspection.get("generatedTextureMipCount") or 0)
    stale_count = int(inspection.get("staleGeneratedCount") or 0)
    runtime_severity = "ok"
    runtime_message = "No generated runtime texture files are present in the local Official folder."
    runtime_fix = ""
    if role == official_external_workflow.ROLE_PROJECT and generated_count:
        runtime_severity = "warning"
        runtime_message = f"{generated_count} generated .texture/.texturemips file(s) are present in an editable Official project."
        runtime_fix = "Keep editable Official projects source-first; generated runtime files belong to deployment/package output."
    elif stale_count:
        runtime_severity = "warning"
        runtime_message = f"{stale_count} generated runtime file(s) are older than their source PNGs."
        runtime_fix = "Regenerate or clear runtime textures before packaging/deployment."
    elif generated_count:
        runtime_message = f"{generated_count} generated runtime texture file(s) are present."
    rows.append(_health_row(
        "generated_runtime_files",
        runtime_severity,
        "Generated runtime files",
        runtime_message,
        runtime_fix,
        {"generatedTextureCount": int(inspection.get("generatedTextureCount") or 0), "generatedTextureMipCount": int(inspection.get("generatedTextureMipCount") or 0), "staleGeneratedCount": stale_count},
    ))

    for index, warning in enumerate(warnings):
        rows.append(_health_row(
            f"json_warning_{index}",
            "warning",
            "JSON warning",
            warning,
            "",
            {},
        ))

    return _health_result(
        rows,
        ok_summary="Official local validation is ready.",
        warning_summary="Official local validation has warnings.",
        error_summary="Official local validation is blocked.",
    )


def is_official_external_entry(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 _local_root(entry: dict[str, Any]) -> Path | None:
    raw = str(entry.get("local_root") or "").strip()
    return Path(raw).expanduser() if raw else None


def _mtime_ns(path: Path) -> int:
    try:
        return path.stat().st_mtime_ns
    except OSError:
        return 0


def _runtime_rows_for_source(source: dict[str, Any], local_root: Path) -> list[dict[str, Any]]:
    source_name = str(source.get("name") or source.get("path") or "").strip()
    source_path = Path(str(source.get("path") or local_root / source_name))
    source_stem = source_path.stem
    source_mtime = _mtime_ns(source_path)
    rows: list[dict[str, Any]] = []
    for suffix in RUNTIME_TEXTURE_SUFFIXES:
        runtime_path = source_path.with_suffix(suffix)
        exists = runtime_path.exists() and runtime_path.is_file()
        runtime_mtime = _mtime_ns(runtime_path) if exists else 0
        rows.append(
            {
                "source": source_name,
                "sourceStem": source_stem,
                "kind": suffix.lstrip("."),
                "name": runtime_path.name,
                "path": str(runtime_path),
                "exists": exists,
                "stale": bool(exists and source_mtime and runtime_mtime and runtime_mtime < source_mtime),
                "sourceMtimeNs": source_mtime,
                "runtimeMtimeNs": runtime_mtime,
            }
        )
    return rows


def inspect_entry(entry: dict[str, Any] | None) -> dict[str, Any]:
    data = entry if isinstance(entry, dict) else {}
    if not is_official_external_entry(data):
        return {"supported": False, "reason": "not an official external livery"}

    root = _local_root(data)
    if root is None:
        return {"supported": True, "valid": False, "reason": "local_root is missing"}
    if not root.exists() or not root.is_dir():
        return {"supported": True, "valid": False, "reason": f"local_root not found: {root}", "localRoot": str(root)}

    inspected = official_external_liveries.inspect_external_livery_folder(root)
    texture_set = inspected.get("textureSet") if isinstance(inspected.get("textureSet"), dict) else {}
    source_textures = inspected.get("sourceTextures") if isinstance(inspected.get("sourceTextures"), list) else []
    runtime_rows: list[dict[str, Any]] = []
    for source in source_textures:
        if isinstance(source, dict):
            runtime_rows.extend(_runtime_rows_for_source(source, root))

    stale_rows = [row for row in runtime_rows if row.get("stale")]
    missing_runtime_rows = [row for row in runtime_rows if not row.get("exists")]
    material_batches = inspected.get("materialBatchNames") if isinstance(inspected.get("materialBatchNames"), list) else []
    texture_slots = texture_set.get("slots") if isinstance(texture_set.get("slots"), list) else []

    result = {
        "supported": True,
        "valid": bool(inspected.get("valid")),
        "officialExternalRole": official_external_workflow.role_for_entry(data),
        "localRoot": str(root),
        "externalLiveryJson": str(inspected.get("externalLiveryJson") or root / official_external_liveries.EXTERNAL_LIVERY_JSON),
        "carGuid": str(inspected.get("carGuid") or data.get("car_guid") or ""),
        "carGuidValid": bool(inspected.get("carGuidValid")),
        "materialBatchNames": material_batches,
        "materialBatchCount": len(material_batches),
        "materials": inspected.get("materials") if isinstance(inspected.get("materials"), list) else [],
        "textureSlots": texture_slots,
        "textureSlotCount": len(texture_slots),
        "sourceTextures": source_textures,
        "sourceTextureCount": len(source_textures),
        "generatedTextures": inspected.get("generatedTextures") if isinstance(inspected.get("generatedTextures"), list) else [],
        "generatedTextureCount": int((inspected.get("textureCounts") or {}).get("texture") or 0),
        "generatedTextureMipCount": int((inspected.get("textureCounts") or {}).get("texturemips") or 0),
        "runtimeTextureRows": runtime_rows,
        "missingRuntimeCount": len(missing_runtime_rows),
        "staleGeneratedRows": stale_rows,
        "staleGeneratedCount": len(stale_rows),
        "missingTextureReferenceCount": int(inspected.get("missingTextureReferenceCount") or 0),
        "projectAssets": official_external_project_assets.inspect_project(root)
        if official_external_workflow.role_for_entry(data) == official_external_workflow.ROLE_PROJECT
        else {},
        "errors": list(inspected.get("errors") or []),
        "warnings": list(inspected.get("warnings") or []),
    }
    result["localValidation"] = local_validation_from_inspection(data, result)
    return result
