from __future__ import annotations

import argparse
import sys
from pathlib import Path

import yaml


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_EVAL_DIRS = [ROOT / "evals", ROOT / "plugins"]
CHECK_TYPES = {"contains", "not_contains", "regex", "max_sentences", "refuses", "requires_metadata"}


def iter_eval_files(paths: list[Path]) -> list[Path]:
    files: list[Path] = []
    for path in paths:
        if path.is_file() and path.suffix.lower() in {".yaml", ".yml"}:
            files.append(path)
        elif path.is_dir():
            files.extend(
                candidate
                for candidate in path.rglob("*.yaml")
                if "evals" in candidate.parts
            )
            files.extend(
                candidate
                for candidate in path.rglob("*.yml")
                if "evals" in candidate.parts
            )
    return sorted(set(files))


def validate_eval(path: Path) -> list[str]:
    errors: list[str] = []
    with path.open("r", encoding="utf-8") as handle:
        data = yaml.safe_load(handle)

    if not isinstance(data, dict):
        return [f"{path}: eval file must be a YAML mapping"]

    for field in ("id", "name", "type", "cases"):
        if field not in data:
            errors.append(f"{path}: missing required field `{field}`")

    cases = data.get("cases")
    if not isinstance(cases, list) or not cases:
        errors.append(f"{path}: cases must be a non-empty list")
        return errors

    for index, case in enumerate(cases):
        prefix = f"{path}: cases[{index}]"
        if not isinstance(case, dict):
            errors.append(f"{prefix}: case must be a mapping")
            continue
        if not case.get("id"):
            errors.append(f"{prefix}: id is required")
        messages = case.get("messages")
        if not isinstance(messages, list) or not messages:
            errors.append(f"{prefix}: messages must be a non-empty list")
        else:
            for msg_index, message in enumerate(messages):
                msg_prefix = f"{prefix}.messages[{msg_index}]"
                if not isinstance(message, dict):
                    errors.append(f"{msg_prefix}: message must be a mapping")
                    continue
                if message.get("role") not in {"system", "user", "assistant", "tool"}:
                    errors.append(f"{msg_prefix}: invalid role `{message.get('role')}`")
                content = message.get("content")
                if not isinstance(content, str) or not content.strip():
                    errors.append(f"{msg_prefix}: content is required")
        checks = case.get("checks")
        if not isinstance(checks, list) or not checks:
            errors.append(f"{prefix}: checks must be a non-empty list")
        else:
            for check_index, check in enumerate(checks):
                check_prefix = f"{prefix}.checks[{check_index}]"
                if isinstance(check, str):
                    if not check.strip():
                        errors.append(f"{check_prefix}: check text is required")
                    continue
                if not isinstance(check, dict):
                    errors.append(f"{check_prefix}: check must be a string or mapping")
                    continue
                check_type = check.get("type")
                if check_type not in CHECK_TYPES:
                    errors.append(f"{check_prefix}: invalid check type `{check_type}`")
                if check_type in {"contains", "not_contains", "regex"}:
                    value = check.get("value")
                    if not isinstance(value, str) or not value.strip():
                        errors.append(f"{check_prefix}: value is required for {check_type}")
                if check_type == "max_sentences":
                    value = check.get("value")
                    if not isinstance(value, int) or value < 1:
                        errors.append(f"{check_prefix}: value must be a positive integer")

    return errors


def main() -> int:
    parser = argparse.ArgumentParser(description="Validate eval YAML files.")
    parser.add_argument("paths", nargs="*", type=Path)
    args = parser.parse_args()

    files = iter_eval_files(args.paths or DEFAULT_EVAL_DIRS)
    if not files:
        print("No eval files found.")
        return 0

    errors: list[str] = []
    for path in files:
        try:
            errors.extend(validate_eval(path))
        except Exception as exc:
            errors.append(f"{path}: failed to parse: {exc}")

    if errors:
        print("Eval validation failed:", file=sys.stderr)
        for error in errors:
            print(f"- {error}", file=sys.stderr)
        return 1

    print(f"Validated {len(files)} eval file(s).")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
