"""ND0202: Top-level node key value type validation."""

import json
from importlib.resources import files
from pathlib import Path
from typing import Any

from valn8n_lib.rules.registry import SourceMap, register
from valn8n_lib.source_map import lookup_line
from valn8n_lib.types import Diagnostic, Location, RuleCode, Severity, SuggestionLevel

_data_text = files("valn8n_lib.data").joinpath("node_keys.json").read_text()
_data = json.loads(_data_text)
KEY_TYPES: dict[str, dict[str, Any]] = _data.get("key_types", {})

_RULE_CODE = RuleCode("ND0202", Severity.ERROR)

# Keys whose values are complex structures validated elsewhere
_SKIP_KEYS = {"parameters", "credentials"}


def _check_type(value: Any, type_desc: dict[str, Any]) -> str | None:  # noqa: PLR0911, PLR0912
    """Check value against type descriptor. Returns error message or None if valid."""
    vtype = type_desc.get("type")

    match vtype:
        case "string":
            if not isinstance(value, str):
                return f"expected string, got {type(value).__name__}"

        case "number":
            if isinstance(value, bool) or not isinstance(value, (int, float)):
                return f"expected number, got {type(value).__name__}"

        case "boolean":
            if not isinstance(value, bool):
                return f"expected boolean, got {type(value).__name__}"

        case "tuple":
            if not isinstance(value, list):
                return f"expected array, got {type(value).__name__}"
            items = type_desc.get("items", [])
            if items and len(value) != len(items):
                return f"expected array of length {len(items)}, got length {len(value)}"
            for i, (elem, expected_item_type) in enumerate(zip(value, items, strict=False)):
                if expected_item_type == "number":
                    if isinstance(elem, bool) or not isinstance(elem, (int, float)):
                        return f"element [{i}]: expected number, got {type(elem).__name__}"
                elif expected_item_type == "string":
                    if not isinstance(elem, str):
                        return f"element [{i}]: expected string, got {type(elem).__name__}"
                elif expected_item_type == "boolean" and not isinstance(elem, bool):
                    return f"element [{i}]: expected boolean, got {type(elem).__name__}"

        case "enum":
            enum_values = type_desc.get("values", [])
            if not isinstance(value, str):
                return f"expected string (enum), got {type(value).__name__}"
            if value not in enum_values:
                return f"invalid enum value {value!r}; valid values: {', '.join(enum_values)}"

        case "record" | "object":
            if not isinstance(value, dict):
                return f"expected object, got {type(value).__name__}"

        case "ref":
            # Reference types (e.g. INodeParametersSchema) — skip, validated elsewhere
            return None

        case _:
            pass

    return None


@register("ND0202", Severity.ERROR, description="Wrong type for node key value")
def check_node_key_value_types(
    workflow: dict[str, Any],
    path: Path,
    source_map: SourceMap = None,
    suggestions: SuggestionLevel = SuggestionLevel.FULL,
) -> list[Diagnostic]:
    """Flag node keys whose values have the wrong type."""
    diagnostics: list[Diagnostic] = []
    nodes = workflow.get("nodes", [])
    file = str(path)

    for node_index, node in enumerate(nodes):
        if not isinstance(node, dict):
            continue
        node_name = node.get("name", "<unnamed>")

        for key, value in node.items():
            if key in _SKIP_KEYS:
                continue
            type_desc = KEY_TYPES.get(key)
            if type_desc is None:
                continue

            error = _check_type(value, type_desc)
            if error is not None:
                pointer = f"/nodes/{node_index}/{key}"
                line = lookup_line(source_map, pointer)
                diagnostics.append(
                    Diagnostic(
                        file=file,
                        location=Location(node_name, key),
                        code=_RULE_CODE,
                        message=f'Wrong type for "{key}": {error}',
                        line=line,
                    )
                )

    return diagnostics
