"""
AI-LSC — Registry schema validation.

Validates that registry entries conform to the expected schema.  This is
intended for CI / developer tooling, not for hot-path runtime checks
(so a small upfront cost is acceptable).

Usage::

    from ai_lsc.registry.validator import validate_registry

    errors = validate_registry(registry_data)
    if errors:
        for e in errors:
            print(f"  - {e}")
"""

from __future__ import annotations

from typing import Any

# Fields that every registry entry must contain.
_REQUIRED_FIELDS: set[str] = {
    "name", "level", "layer", "role", "category",
    "installer", "launcher", "deps", "description", "flags",
}

# Valid values for certain fields — must match InstallerType / LauncherType enums.
_VALID_INSTALLER_TYPES: set[str] = {
    "ollama", "uv", "pipx", "pip", "npm",
    "git", "git_node", "pacman", "dnf", "apt",
    "script", "custom",
}
_VALID_LAUNCHER_TYPES: set[str] = {
    "systemd", "tmux", "desktop", "lxc",
}

# Optional installer fields (allowed but not required).
_OPTIONAL_INSTALLER_FIELDS: set[str] = {
    "type", "pkg", "cmd", "post_install", "update_cmd", "env_overrides",
}
_OPTIONAL_LAUNCHER_FIELDS: set[str] = {
    "type", "cmd", "default_port",
}
_OPTIONAL_FILESYSTEM_FIELDS: set[str] = {
    "install", "config", "cache", "data", "logs", "runtime", "models",
}


def _check_entry(tool_id: str, entry: dict[str, Any]) -> list[str]:
    """Return a list of validation error strings for a single entry."""
    errors: list[str] = []

    # Missing required fields
    missing = _REQUIRED_FIELDS - entry.keys()
    if missing:
        errors.append(f"{tool_id}: missing fields {sorted(missing)}")

    # Level must be 1–13
    level = entry.get("level")
    if isinstance(level, int) and not (1 <= level <= 13):
        errors.append(f"{tool_id}: level {level} out of range 1-13")
    elif not isinstance(level, int):
        errors.append(f"{tool_id}: level is not an int ({level!r})")

    # Installer type
    inst = entry.get("installer", {})
    if isinstance(inst, dict):
        itype = inst.get("type")
        if itype and itype not in _VALID_INSTALLER_TYPES:
            errors.append(
                f"{tool_id}: unknown installer type {itype!r} "
                f"(valid: {sorted(_VALID_INSTALLER_TYPES)})"
            )
        # Script-type installers must include cmd
        if itype == "script" and not inst.get("cmd"):
            errors.append(
                f"{tool_id}: installer type 'script' requires 'cmd'"
            )
        # Warn if script cmd doesn't contain {tools_root}
        if itype == "script" and inst.get("cmd"):
            cmd_str = inst["cmd"]
            if "{tools_root}" not in cmd_str and tool_id != "ollama":
                errors.append(
                    f"{tool_id}: script installer cmd should reference "
                    f"{{{{tools_root}}}} to avoid polluting system dirs"
                )

    # Launcher type
    launch = entry.get("launcher", {})
    if isinstance(launch, dict):
        ltype = launch.get("type")
        if ltype and ltype not in _VALID_LAUNCHER_TYPES:
            errors.append(
                f"{tool_id}: unknown launcher type {ltype!r} "
                f"(valid: {sorted(_VALID_LAUNCHER_TYPES)})"
            )

    # Filesystem spec (optional but validated if present)
    fs = entry.get("filesystem", {})
    if isinstance(fs, dict):
        unknown_fs = set(fs.keys()) - _OPTIONAL_FILESYSTEM_FIELDS
        if unknown_fs:
            errors.append(
                f"{tool_id}: unknown filesystem fields {sorted(unknown_fs)}"
            )

    # deps must be a list of strings
    deps = entry.get("deps")
    if not isinstance(deps, list):
        errors.append(f"{tool_id}: deps is not a list")
    else:
        non_str = [d for d in deps if not isinstance(d, str)]
        if non_str:
            errors.append(
                f"{tool_id}: deps contains non-string items: {non_str}"
            )

    # flags must be a dict of bools
    flags = entry.get("flags", {})
    if not isinstance(flags, dict):
        errors.append(f"{tool_id}: flags is not a dict")
    else:
        non_bool = {k: v for k, v in flags.items()
                    if not isinstance(v, bool)}
        if non_bool:
            errors.append(
                f"{tool_id}: flags contain non-bool values: "
                f"{list(non_bool.keys())}"
            )

    return errors


def validate_registry(data: dict[str, Any]) -> list[str]:
    """Validate an entire registry dict.

    Returns a (possibly empty) list of human-readable error strings.
    An empty list means the registry is valid.
    """
    errors: list[str] = []
    for tool_id, entry in data.items():
        if not isinstance(entry, dict):
            errors.append(f"{tool_id}: entry is not a dict ({type(entry)})")
            continue
        errors.extend(_check_entry(tool_id, entry))
    return errors
