"""Test catalog loading, validation, and schema extraction."""

from __future__ import annotations

import re
from pathlib import Path

import yaml

# Default catalog directory is the built-in catalog shipped with the package
_BUILTIN_CATALOG_DIR = Path(__file__).parent / "catalog"

REQUIRED_FIELDS = {"name", "type", "query"}
VALID_TYPES = {"clause", "operator", "function", "expression", "data_type"}
VALIDATION_FIELDS = {"expect_success", "expected_columns", "expected_rows", "expected_contains", "expect_error"}


def load_catalog(catalog_dir: str | Path | None = None) -> list[dict]:
    """Load all YAML test files from a catalog directory.

    Args:
        catalog_dir: Path to catalog directory. Uses built-in catalog if None.

    Returns:
        List of test definition dicts.
    """
    catalog_path = Path(catalog_dir) if catalog_dir else _BUILTIN_CATALOG_DIR

    if not catalog_path.is_dir():
        raise FileNotFoundError(f"Catalog directory not found: {catalog_path}")

    yaml_files = sorted(catalog_path.glob("*.yaml"))
    if not yaml_files:
        raise FileNotFoundError(f"No YAML files found in catalog directory: {catalog_path}")

    tests = []
    for yaml_file in yaml_files:
        with open(yaml_file) as f:
            data = yaml.safe_load(f)
        if data and "tests" in data:
            for test in data["tests"]:
                test["_source_file"] = yaml_file.name
                tests.append(test)

    return tests


def validate_catalog(tests: list[dict]) -> list[str]:
    """Validate test definitions for correctness.

    Returns:
        List of error messages. Empty list means all tests are valid.
    """
    errors = []
    seen_names = set()

    for i, test in enumerate(tests):
        prefix = f"Test #{i + 1}"
        if "name" in test:
            prefix = f"Test '{test['name']}'"

        # Check required fields
        for field in REQUIRED_FIELDS:
            if field not in test:
                errors.append(f"{prefix}: missing required field '{field}'")

        # Check type is valid
        if "type" in test and test["type"] not in VALID_TYPES:
            errors.append(
                f"{prefix}: invalid type '{test['type']}'. "
                f"Must be one of: {sorted(VALID_TYPES)}"
            )

        # Check at least one validation field
        if not any(test.get(f) for f in VALIDATION_FIELDS):
            errors.append(f"{prefix}: must have at least one validation field ({', '.join(sorted(VALIDATION_FIELDS))})")

        # Check for duplicate names
        name = test.get("name")
        if name:
            if name in seen_names:
                errors.append(f"{prefix}: duplicate test name")
            seen_names.add(name)

    return errors


def extract_schema(tests: list[dict]) -> dict:
    """Extract labels, edge types, and properties from test catalog.

    Returns:
        {"tags": {"Person": {"name", "age"}, ...},
         "edges": {"KNOWS": set(), ...}}
    """
    tags: dict[str, set[str]] = {}
    edges: dict[str, set[str]] = {}

    node_pattern = re.compile(r"\(:(\w+)\s*(?:\{([^}]*)\})?")
    edge_pattern = re.compile(r"\[:(\w+)\s*(?:\{([^}]*)\})?")
    prop_pattern = re.compile(r"(\w+)\s*:")

    for test in tests:
        queries = list(test.get("setup", [])) + [test.get("query", "")]
        for q in queries:
            for match in node_pattern.finditer(q):
                label = match.group(1)
                if label not in tags:
                    tags[label] = set()
                if match.group(2):
                    for prop in prop_pattern.findall(match.group(2)):
                        tags[label].add(prop)
            for match in edge_pattern.finditer(q):
                etype = match.group(1)
                if etype not in edges:
                    edges[etype] = set()
                if match.group(2):
                    for prop in prop_pattern.findall(match.group(2)):
                        edges[etype].add(prop)

    return {"tags": tags, "edges": edges}
