"""Extracted validation helpers for ResourceTypeSpec.

Keeps ``resource_type.py`` under the 500-line CONTRIBUTING limit by
housing the virtual-type and auto-discovery cross-field validators
in a separate internal module.
"""

from __future__ import annotations

import re
from typing import Any

_BUILTIN_NAME_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*$")
_NAMESPACED_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*/[a-zA-Z][a-zA-Z0-9_-]*$")

MAX_SCAN_DEPTH = 10

# All known built-in type names.  Extracted here so that
# ``ResourceTypeSpec.BUILTIN_NAMES`` stays a one-liner reference.
BUILTIN_TYPE_NAMES: frozenset[str] = frozenset(
    {
        # -- Git / Filesystem / Container --
        "git-checkout",
        "fs-directory",
        "fs-mount",
        "fs-file",
        "container-exec-env",
        "container-image",
        "container-instance",
        "container-mount",
        "container-network",
        "container-port",
        "container-runtime",
        "container-volume",
        "devcontainer-instance",
        "devcontainer-file",
        "postgres",
        "mysql",
        "sqlite",
        "duckdb",
        # -- Generic cloud base types --
        "cloud-account",
        "cloud-region",
        "cloud-network",
        "cloud-subnet",
        "cloud-security-group",
        "cloud-load-balancer",
        "cloud-compute-instance",
        "cloud-object-store",
        "cloud-block-storage",
        "cloud-identity-principal",
        "cloud-role",
        "cloud-policy",
        "cloud-log-group",
        "cloud-alarm",
        "cloud-queue",
        "cloud-topic",
        "cloud-container-repo",
        "cloud-container-cluster",
        "cloud-container-service",
        # -- AWS provider types --
        "aws-account",
        "aws-region",
        "aws-vpc",
        "aws-subnet",
        "aws-igw",
        "aws-nat-gw",
        "aws-route-table",
        "aws-nacl",
        "aws-security-group",
        "aws-alb",
        "aws-nlb",
        "aws-target-group",
        "aws-listener",
        "aws-ec2-instance",
        "aws-ami",
        "aws-launch-template",
        "aws-asg",
        "aws-s3-bucket",
        "aws-ebs-volume",
        "aws-efs-filesystem",
        "aws-iam-user",
        "aws-iam-role",
        "aws-iam-policy",
        "aws-iam-instance-profile",
        "aws-cloudwatch-log-group",
        "aws-cloudwatch-alarm",
        "aws-cloudwatch-metric",
        "aws-eventbridge-bus",
        "aws-eventbridge-rule",
        "aws-eventbridge-target",
        "aws-sqs-queue",
        "aws-sns-topic",
        "aws-sns-subscription",
        "aws-ecr-repo",
        "aws-ecs-cluster",
        "aws-ecs-service",
        "aws-ecs-task-def",
        "aws-eks-cluster",
        "aws-eks-nodegroup",
        # -- GCP / Azure (flat, hierarchy pending) --
        "gcp",
        "azure",
        # Deferred physical resource types (#330)
        "fs-hardlink",
        "fs-symlink",
        "git",
        "git-branch",
        "git-commit",
        "git-remote",
        "git-stash",
        "git-submodule",
        "git-tag",
        "git-tree",
        "git-tree-entry",
        # Virtual core resource types (#329)
        "branch",
        "commit",
        "directory",
        "file",
        "tag",
        "tree",
        # Deferred virtual resource types (#331)
        "remote",
        "submodule",
        "symlink",
        # LSP resource types (#832)
        "executable",
        "lsp-server",
        "lsp-workspace",
        "lsp-document",
    }
)


def validate_virtual_type(
    name: str,
    equivalence: dict[str, Any] | None,
    user_addable: bool,
    sandbox_strategy_value: str,
    handler: str | None,
    capabilities: dict[str, bool],
) -> None:
    """Validate constraints specific to virtual resource types."""
    if equivalence is None:
        raise ValueError(
            f"Virtual resource type '{name}' requires an "
            "'equivalence' configuration for deduplication."
        )
    if "criteria" not in equivalence:
        raise ValueError(
            f"Virtual resource type '{name}' equivalence must "
            "contain a 'criteria' list per spec."
        )
    criteria = equivalence["criteria"]
    if not isinstance(criteria, list) or len(criteria) == 0:
        raise ValueError(
            f"Virtual resource type '{name}' equivalence "
            "'criteria' must be a non-empty list."
        )
    for i, item in enumerate(criteria):
        if not isinstance(item, str):
            raise ValueError(
                f"Virtual resource type '{name}' equivalence "
                f"criteria[{i}] must be a string, got {type(item).__name__}."
            )
        if not item.strip():
            raise ValueError(
                f"Virtual resource type '{name}' equivalence "
                f"criteria[{i}] must be a non-empty string."
            )
    if user_addable:
        raise ValueError(
            f"Virtual resource type '{name}' must not be "
            "user_addable (virtual types are system-managed)."
        )
    if sandbox_strategy_value != "none":
        raise ValueError(
            f"Virtual resource type '{name}' must have sandbox_strategy 'none'."
        )
    if handler is not None:
        raise ValueError(
            f"Virtual resource type '{name}' must not have "
            "a handler (virtual types have no physical backing)."
        )
    if capabilities and any(
        capabilities.get(k) for k in ("read", "write", "sandbox", "checkpoint")
    ):
        raise ValueError(
            f"Virtual resource type '{name}' must have all capabilities set to false."
        )


def validate_auto_discovery(
    name: str,
    auto_discovery: dict[str, Any],
    builtin_names: frozenset[str],
) -> None:
    """Validate auto-discovery configuration.

    Two schemas coexist in the same ``dict[str, Any]`` field:
      - Rules-based (git/fs): ``{enabled, scan_depth, rules: [{type, pattern}]}``
      - Trigger-based (devcontainer): ``{trigger_types, scan_paths}``

    This validator only checks rules-based keys when ``rules`` is present.
    """
    rules = auto_discovery.get("rules")
    if isinstance(rules, list):
        for i, rule in enumerate(rules):
            if not isinstance(rule, dict):
                raise ValueError(
                    f"'{name}' auto_discovery.rules[{i}] must "
                    f"be a dict, got {type(rule).__name__}."
                )
            rule_type = rule.get("type", "")
            if not isinstance(rule_type, str) or not rule_type.strip():
                raise ValueError(
                    f"'{name}' auto_discovery.rules[{i}].type "
                    "must be a non-empty string."
                )
            if not _BUILTIN_NAME_RE.match(rule_type) and not _NAMESPACED_RE.match(
                rule_type
            ):
                raise ValueError(
                    f"'{name}' auto_discovery.rules[{i}].type "
                    f"'{rule_type}' is not a valid resource type name."
                )
            if (
                "/" not in rule_type
                and _BUILTIN_NAME_RE.match(rule_type)
                and rule_type not in builtin_names
            ):
                raise ValueError(
                    f"'{name}' auto_discovery.rules[{i}].type "
                    f"'{rule_type}' is not a known built-in type."
                )
            pattern = rule.get("pattern", "")
            if not isinstance(pattern, str) or not pattern.strip():
                raise ValueError(
                    f"'{name}' auto_discovery.rules[{i}].pattern "
                    "must be a non-empty string."
                )
    scan_depth = auto_discovery.get("scan_depth")
    if scan_depth is not None:
        if not isinstance(scan_depth, int) or scan_depth < 1:
            raise ValueError(
                f"'{name}' auto_discovery.scan_depth must be "
                f"a positive integer, got {scan_depth!r}."
            )
        if scan_depth > MAX_SCAN_DEPTH:
            raise ValueError(
                f"'{name}' auto_discovery.scan_depth={scan_depth} "
                f"exceeds maximum of {MAX_SCAN_DEPTH}."
            )


def validate_self_referential(
    name: str,
    parent_types: list[str] | None,
    child_types: list[str] | None,
    auto_discovery: dict[str, Any] | None,
) -> None:
    """Ensure self-referential types have bounded scan_depth."""
    if (
        auto_discovery is not None
        and name in (parent_types or [])
        and name in (child_types or [])
    ):
        sd = auto_discovery.get("scan_depth")
        if sd is None:
            raise ValueError(
                f"'{name}' is self-referential (in both parent_types "
                "and child_types) but has no scan_depth bound. "
                "Recursive types MUST specify scan_depth."
            )
