"""Step definitions for resource_type_deferred_physical.feature.

Tests deferred physical resource types: git object taxonomy and
filesystem link types (#330).
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

import yaml
from behave import given, then, when

from cleveragents.application.services._resource_registry_data import (
    BUILTIN_TYPES,
)
from cleveragents.domain.models.core.resource_type import ResourceTypeSpec

_EXAMPLES_DIR = (
    Path(__file__).resolve().parent.parent.parent / "examples" / "resource-types"
)


# ── Given steps ──────────────────────────────────────────────


@given('the built-in "{type_name}" YAML for phys_deferred')
def step_builtin_yaml_for_phys_deferred(context: object, type_name: str) -> None:
    """Load a built-in YAML file by type name."""
    path = _EXAMPLES_DIR / f"{type_name}.yaml"
    assert path.exists(), f"YAML file not found: {path}"
    raw: dict[str, Any] = yaml.safe_load(path.read_text(encoding="utf-8"))
    context.phys_deferred_config = raw  # type: ignore[attr-defined]
    context.phys_deferred_yaml_path = path  # type: ignore[attr-defined]


@given("the ResourceTypeSpec BUILTIN_NAMES set for phys_deferred")
def step_builtin_names_set_for_phys_deferred(context: object) -> None:
    """Load the BUILTIN_NAMES frozenset."""
    context.phys_deferred_builtin_names = ResourceTypeSpec.BUILTIN_NAMES  # type: ignore[attr-defined]


@given("the _BUILTIN_TYPES list for phys_deferred")
def step_builtin_types_list_for_phys_deferred(context: object) -> None:
    """Load the BUILTIN_TYPES list."""
    context.phys_deferred_builtin_types = BUILTIN_TYPES  # type: ignore[attr-defined]


# ── When steps ───────────────────────────────────────────────


@when("I load the YAML via from_config for phys_deferred")
def step_load_yaml_for_phys_deferred(context: object) -> None:
    """Parse YAML config into a ResourceTypeSpec."""
    config: dict[str, Any] = context.phys_deferred_config  # type: ignore[attr-defined]
    context.phys_deferred_spec = ResourceTypeSpec.from_config(config)  # type: ignore[attr-defined]


# ── Then steps ───────────────────────────────────────────────


@then("the spec should be valid for phys_deferred")
def step_spec_valid_for_phys_deferred(context: object) -> None:
    spec = context.phys_deferred_spec  # type: ignore[attr-defined]
    assert spec is not None
    assert isinstance(spec, ResourceTypeSpec)


@then('the spec name should be "{name}" for phys_deferred')
def step_spec_name_for_phys_deferred(context: object, name: str) -> None:
    assert context.phys_deferred_spec.name == name  # type: ignore[attr-defined]


@then('the spec resource_kind should be "{kind}" for phys_deferred')
def step_spec_kind_for_phys_deferred(context: object, kind: str) -> None:
    actual = context.phys_deferred_spec.resource_kind  # type: ignore[attr-defined]
    assert str(actual) == kind or actual.value == kind, (
        f"Expected resource_kind '{kind}', got '{actual}'"
    )


@then('the spec sandbox_strategy should be "{strategy}" for phys_deferred')
def step_spec_sandbox_for_phys_deferred(context: object, strategy: str) -> None:
    actual = context.phys_deferred_spec.sandbox_strategy  # type: ignore[attr-defined]
    assert str(actual) == strategy or actual.value == strategy, (
        f"Expected sandbox_strategy '{strategy}', got '{actual}'"
    )


@then("the spec user_addable should be true for phys_deferred")
def step_spec_user_addable_true_for_phys_deferred(context: object) -> None:
    assert context.phys_deferred_spec.user_addable is True  # type: ignore[attr-defined]


@then("the spec user_addable should be false for phys_deferred")
def step_spec_user_addable_false_for_phys_deferred(context: object) -> None:
    assert context.phys_deferred_spec.user_addable is False  # type: ignore[attr-defined]


@then("the spec built_in should be true for phys_deferred")
def step_spec_builtin_true_for_phys_deferred(context: object) -> None:
    assert context.phys_deferred_spec.built_in is True  # type: ignore[attr-defined]


@then('the spec parent_types should contain "{parent}" for phys_deferred')
def step_spec_parent_contains_for_phys_deferred(context: object, parent: str) -> None:
    parents = context.phys_deferred_spec.parent_types  # type: ignore[attr-defined]
    assert parent in parents, f"Expected '{parent}' in parent_types, got {parents}"


@then('the spec child_types should contain "{child}" for phys_deferred')
def step_spec_child_contains_for_phys_deferred(context: object, child: str) -> None:
    children = context.phys_deferred_spec.child_types  # type: ignore[attr-defined]
    assert child in children, f"Expected '{child}' in child_types, got {children}"


@then("the spec should have empty child_types for phys_deferred")
def step_spec_empty_children_for_phys_deferred(context: object) -> None:
    children = context.phys_deferred_spec.child_types  # type: ignore[attr-defined]
    assert len(children) == 0, f"Expected empty child_types, got {children}"


@then("the spec capabilities read should be true for phys_deferred")
def step_spec_caps_read_true_for_phys_deferred(context: object) -> None:
    caps = context.phys_deferred_spec.capabilities  # type: ignore[attr-defined]
    assert caps["read"] is True, f"Expected read=True, got {caps['read']}"


@then("the spec capabilities write should be false for phys_deferred")
def step_spec_caps_write_false_for_phys_deferred(context: object) -> None:
    caps = context.phys_deferred_spec.capabilities  # type: ignore[attr-defined]
    assert caps["write"] is False, f"Expected write=False, got {caps['write']}"


@then("the spec capabilities write should be true for phys_deferred")
def step_spec_caps_write_true_for_phys_deferred(context: object) -> None:
    caps = context.phys_deferred_spec.capabilities  # type: ignore[attr-defined]
    assert caps["write"] is True, f"Expected write=True, got {caps['write']}"


@then("the spec auto_discovery should be enabled for phys_deferred")
def step_spec_auto_discovery_enabled_for_phys_deferred(context: object) -> None:
    spec = context.phys_deferred_spec  # type: ignore[attr-defined]
    assert spec.auto_discovery is not None, "auto_discovery should not be None"
    assert spec.auto_discovery.get("enabled") is True, (
        f"auto_discovery.enabled should be True, got {spec.auto_discovery}"
    )


@then("the spec auto_discovery scan_depth should be {depth:d} for phys_deferred")
def step_spec_auto_discovery_depth_for_phys_deferred(
    context: object, depth: int
) -> None:
    spec = context.phys_deferred_spec  # type: ignore[attr-defined]
    assert spec.auto_discovery is not None, "auto_discovery should not be None"
    actual_depth = spec.auto_discovery.get("scan_depth")
    assert actual_depth == depth, f"Expected scan_depth={depth}, got {actual_depth}"


@then("the spec auto_discovery should be none for phys_deferred")
def step_spec_auto_discovery_none_for_phys_deferred(context: object) -> None:
    spec = context.phys_deferred_spec  # type: ignore[attr-defined]
    assert spec.auto_discovery is None, (
        f"Expected auto_discovery=None, got {spec.auto_discovery}"
    )


@then('BUILTIN_NAMES should contain "{name}" for phys_deferred')
def step_builtin_names_contains_for_phys_deferred(context: object, name: str) -> None:
    names = context.phys_deferred_builtin_names  # type: ignore[attr-defined]
    assert name in names, f"Expected '{name}' in BUILTIN_NAMES, got {names}"


@then('_BUILTIN_TYPES should contain entry "{name}" for phys_deferred')
def step_builtin_types_contains_for_phys_deferred(context: object, name: str) -> None:
    types_list: list[dict[str, Any]] = context.phys_deferred_builtin_types  # type: ignore[attr-defined]
    names = [t["name"] for t in types_list]
    assert name in names, f"Expected '{name}' in _BUILTIN_TYPES names, got {names}"


# ── Negative tests: auto_discovery validation ────────────


def _base_physical_config(**overrides: Any) -> dict[str, Any]:
    """Create a minimal physical type config."""
    config: dict[str, Any] = {
        "name": "testns/phys-test",
        "resource_kind": "physical",
        "sandbox_strategy": "none",
        "user_addable": False,
        "built_in": False,
    }
    config.update(overrides)
    return config


@given("a physical type config with invalid auto_discovery rule type for phys_deferred")
def step_invalid_rule_type(context: object) -> None:
    context.phys_deferred_test_config = _base_physical_config(  # type: ignore[attr-defined]
        auto_discovery={
            "scan_depth": 1,
            "rules": [{"type": "nonexistent-typo", "pattern": "*"}],
        },
    )


@given("a physical type config with empty auto_discovery pattern for phys_deferred")
def step_empty_pattern(context: object) -> None:
    context.phys_deferred_test_config = _base_physical_config(  # type: ignore[attr-defined]
        auto_discovery={
            "scan_depth": 1,
            "rules": [{"type": "git-checkout", "pattern": ""}],
        },
    )


@given("a physical type config with scan_depth {depth:d} for phys_deferred")
def step_excessive_depth(context: object, depth: int) -> None:
    context.phys_deferred_test_config = _base_physical_config(  # type: ignore[attr-defined]
        auto_discovery={
            "scan_depth": depth,
            "rules": [{"type": "git-checkout", "pattern": "*"}],
        },
    )


@given("a self-referential type config without scan_depth for phys_deferred")
def step_self_ref_no_depth(context: object) -> None:
    context.phys_deferred_test_config = _base_physical_config(  # type: ignore[attr-defined]
        name="testns/recursive",
        parent_types=["testns/recursive"],
        child_types=["testns/recursive"],
        auto_discovery={
            "rules": [{"type": "testns/recursive", "pattern": "*"}],
        },
    )


@when("I attempt to create a ResourceTypeSpec from it for phys_deferred")
def step_attempt_create(context: object) -> None:
    config: dict[str, Any] = context.phys_deferred_test_config  # type: ignore[attr-defined]
    try:
        context.phys_deferred_created = ResourceTypeSpec.from_config(config)  # type: ignore[attr-defined]
        context.phys_deferred_error = None  # type: ignore[attr-defined]
    except Exception as exc:
        context.phys_deferred_created = None  # type: ignore[attr-defined]
        context.phys_deferred_error = exc  # type: ignore[attr-defined]


@then('the phys_deferred creation should fail with "{message}"')
def step_creation_fail(context: object, message: str) -> None:
    err = context.phys_deferred_error  # type: ignore[attr-defined]
    assert err is not None, "Expected error but creation succeeded"
    assert message.lower() in str(err).lower(), (
        f"Error does not mention '{message}': {err}"
    )


# ── as_cli_dict regression steps ─────────────────────────


@given('the built-in "devcontainer-instance" type from registry for phys_deferred')
def step_devcontainer_from_registry(context: object) -> None:
    """Load devcontainer-instance from BUILTIN_TYPES (trigger-based schema)."""
    entry = next(d for d in BUILTIN_TYPES if d["name"] == "devcontainer-instance")
    context.phys_deferred_spec = ResourceTypeSpec.from_config(entry)  # type: ignore[attr-defined]


@then("as_cli_dict should succeed and include auto_discovery for phys_deferred")
def step_cli_dict_auto_discovery(context: object) -> None:
    spec = context.phys_deferred_spec  # type: ignore[attr-defined]
    cli = spec.as_cli_dict()
    assert "auto_discovery" in cli, (
        f"as_cli_dict missing auto_discovery: {list(cli.keys())}"
    )
    ad = cli["auto_discovery"]
    assert isinstance(ad, dict), f"auto_discovery not a dict: {type(ad)}"
