"""Step definitions for resource_type_model.feature."""

import os
import tempfile

from behave import given, then, when

from cleveragents.domain.models.core.resource_type import (
    ResourceKind,
    ResourceTypeSpec,
    SandboxStrategy,
)
from cleveragents.resource.schema import ResourceTypeConfigSchema


def _base_config(
    name: str = "myorg/test-type",
    resource_kind: str = "physical",
    sandbox_strategy: str = "git_worktree",
    built_in: bool = False,
) -> dict:
    """Return a minimal valid resource type config dict."""
    return {
        "name": name,
        "resource_kind": resource_kind,
        "sandbox_strategy": sandbox_strategy,
        "built_in": built_in,
        "description": "Test resource type",
    }


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


@given('a resource type config with name "{name}" and built_in {built_in}')
def step_config_with_name_builtin(context, name, built_in):
    context.rt_config = _base_config(name=name, built_in=built_in.lower() == "true")


@given('a resource type config with resource_kind "{kind}"')
def step_config_with_kind(context, kind):
    context.rt_config = _base_config(resource_kind=kind)


@given('a resource type config with resource_kind "{kind}" and equivalence set')
def step_config_with_kind_and_equiv(context, kind):
    if kind == "virtual":
        config = _base_config(
            resource_kind=kind,
            sandbox_strategy="none",
        )
        config["user_addable"] = False
        config["handler"] = None
        config["capabilities"] = {
            "read": False,
            "write": False,
            "sandbox": False,
            "checkpoint": False,
        }
    else:
        config = _base_config(resource_kind=kind)
    config["equivalence"] = {
        "criteria": ["content_hash"],
        "description": "Test equivalence",
    }
    context.rt_config = config


@given('a resource type config with resource_kind "{kind}" and no equivalence')
def step_config_with_kind_no_equiv(context, kind):
    context.rt_config = _base_config(resource_kind=kind)


@given('a resource type config with sandbox_strategy "{strategy}"')
def step_config_with_sandbox(context, strategy):
    context.rt_config = _base_config(sandbox_strategy=strategy)


@given('a resource type config with a cli_arg named "{name}" of type "{arg_type}"')
def step_config_with_cli_arg(context, name, arg_type):
    config = _base_config()
    config["cli_args"] = [
        {"name": name, "type": arg_type, "required": True, "description": "Test arg"}
    ]
    context.rt_config = config


@given('a resource type config with cli_args "{arg1}" and "{arg2}"')
def step_config_with_two_cli_args(context, arg1, arg2):
    config = _base_config()
    config["cli_args"] = [
        {"name": arg1, "type": "string", "required": True, "description": "Arg 1"},
        {"name": arg2, "type": "string", "required": False, "description": "Arg 2"},
    ]
    context.rt_config = config


@given('a resource type config with parent_types "{parent}"')
def step_config_with_parent_types(context, parent):
    config = _base_config()
    config["parent_types"] = [parent]
    context.rt_config = config


@given('a resource type config with child_types "{child}"')
def step_config_with_child_types(context, child):
    config = _base_config()
    config["child_types"] = [child]
    context.rt_config = config


@given(
    'a resource type config with parent_types_set "{parent}" and child_types_set "{child}"'
)
def step_config_with_parent_and_child(context, parent, child):
    config = _base_config()
    config["parent_types"] = [parent]
    config["child_types"] = [child]
    context.rt_config = config


@given("a resource type config dict without name")
def step_config_without_name(context):
    context.rt_config = {
        "resource_kind": "physical",
        "sandbox_strategy": "git_worktree",
    }


@given("a resource type config dict without resource_kind")
def step_config_without_kind(context):
    context.rt_config = {"name": "myorg/test", "sandbox_strategy": "git_worktree"}


@given("a resource type config dict without sandbox_strategy")
def step_config_without_strategy(context):
    context.rt_config = {"name": "myorg/test", "resource_kind": "physical"}


@given("a full resource type config dict")
def step_full_config(context):
    context.rt_config = _base_config()
    context.rt_config["cli_args"] = [
        {"name": "path", "type": "path", "required": True, "description": "Path"}
    ]
    context.rt_config["parent_types"] = []
    context.rt_config["child_types"] = ["fs-directory"]
    context.rt_config["handler"] = "test.handler:TestHandler"


@given("a valid resource type YAML string")
def step_valid_yaml_string(context):
    context.rt_yaml = """
name: myorg/yaml-test
description: YAML test type
resource_kind: physical
sandbox_strategy: git_worktree
cli_args:
  - name: path
    type: path
    required: true
    description: Path to resource
"""


@given("a valid resource type YAML file")
def step_valid_yaml_file(context):
    content = """
name: myorg/file-test
description: File test type
resource_kind: physical
sandbox_strategy: copy_on_write
cli_args:
  - name: path
    type: path
    required: true
    description: Path
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
        tmp.write(content)
    context.rt_yaml_path = tmp.name


@given('a resource type YAML with env var "${{RT_TEST_HANDLER}}"')
def step_yaml_with_env_var(context):
    context.rt_yaml = """
name: myorg/env-test
description: Env test
resource_kind: physical
sandbox_strategy: none
handler: ${RT_TEST_HANDLER}
"""


@given('the env var "{var}" is set to "{value}"')
def step_set_env_var(context, var, value):
    os.environ[var] = value
    try:
        context._env_vars_to_clean.append(var)
    except (AttributeError, KeyError):
        context._env_vars_to_clean = [var]


@given('a resource type YAML with schema_version "{version}"')
def step_yaml_with_version(context, version):
    context.rt_yaml = f"""
schema_version: "{version}"
name: myorg/version-test
resource_kind: physical
sandbox_strategy: none
"""


@given("a resource type config with default capabilities")
def step_config_default_caps(context):
    context.rt_config = _base_config()


@given("a resource type config with custom capabilities")
def step_config_custom_caps(context):
    config = _base_config()
    config["capabilities"] = {
        "read": True,
        "write": False,
        "sandbox": True,
        "checkpoint": True,
    }
    context.rt_config = config


@given('a resource type config with unknown capability "{cap}"')
def step_config_unknown_cap(context, cap):
    config = _base_config()
    config["capabilities"] = {
        "read": True,
        "write": True,
        "sandbox": True,
        "checkpoint": False,
        cap: True,
    }
    context.rt_config = config


@given("a resource type config with non-bool capability")
def step_config_nonbool_cap(context):
    config = _base_config()
    config["capabilities"] = {
        "read": "yes",
        "write": True,
        "sandbox": True,
        "checkpoint": False,
    }
    context.rt_config = config


@given("a resource type config with a non-mapping cli_arg")
def step_config_nonmapping_cli_arg(context):
    config = _base_config()
    config["cli_args"] = ["not-a-mapping"]
    context.rt_config = config


@given("a full resource type config with handler and auto_discovery")
def step_full_config_with_handler(context):
    context.rt_config = _base_config()
    context.rt_config["cli_args"] = [
        {"name": "path", "type": "path", "required": True, "description": "Path"}
    ]
    context.rt_config["parent_types"] = []
    context.rt_config["child_types"] = ["fs-directory"]
    context.rt_config["handler"] = "test.handler:TestHandler"
    context.rt_config["auto_discovery"] = {"pattern": "*.yaml"}
    context.rt_config["equivalence"] = None


@given("a resource type YAML with invalid cli_arg name")
def step_yaml_invalid_arg_name(context):
    context.rt_yaml = """
name: myorg/test
resource_kind: physical
sandbox_strategy: none
cli_args:
  - name: BadName
    type: string
    required: true
    description: Bad
"""


@given("a resource type YAML with invalid cli_arg type")
def step_yaml_invalid_arg_type(context):
    context.rt_yaml = """
name: myorg/test
resource_kind: physical
sandbox_strategy: none
cli_args:
  - name: count
    type: bigint
    required: true
    description: Bad type
"""


@given("a resource type YAML with invalid name")
def step_yaml_invalid_name(context):
    context.rt_yaml = """
name: "bad!name"
resource_kind: physical
sandbox_strategy: none
"""


@given("a resource type YAML with invalid resource_kind")
def step_yaml_invalid_kind(context):
    context.rt_yaml = """
name: myorg/test
resource_kind: quantum
sandbox_strategy: none
"""


@given("a resource type YAML with invalid sandbox_strategy")
def step_yaml_invalid_strategy(context):
    context.rt_yaml = """
name: myorg/test
resource_kind: physical
sandbox_strategy: teleport
"""


@given("a resource type YAML with custom type no namespace")
def step_yaml_custom_no_namespace(context):
    context.rt_yaml = """
name: no-namespace
resource_kind: physical
sandbox_strategy: none
built_in: false
"""


@given("a resource type YAML with virtual kind no equivalence")
def step_yaml_virtual_no_equiv(context):
    context.rt_yaml = """
name: myorg/virtual-test
resource_kind: virtual
sandbox_strategy: none
"""


@given("a resource type YAML with env var in list")
def step_yaml_env_var_in_list(context):
    context.rt_yaml = """
name: myorg/list-env-test
resource_kind: physical
sandbox_strategy: none
parent_types:
  - ${RT_TEST_PARENT}
"""


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


@when("I create a ResourceTypeSpec from config")
def step_create_spec(context):
    context.rt_spec = ResourceTypeSpec.from_config(context.rt_config)


@when("I try to create a ResourceTypeSpec from config")
def step_try_create_spec(context):
    try:
        context.rt_spec = ResourceTypeSpec.from_config(context.rt_config)
        context.rt_error = None
    except (ValueError, Exception) as exc:
        context.rt_error = str(exc)


@when("I try to call from_config")
def step_try_from_config(context):
    try:
        context.rt_spec = ResourceTypeSpec.from_config(context.rt_config)
        context.rt_error = None
    except ValueError as exc:
        context.rt_error = str(exc)


@when("I call from_config")
def step_call_from_config(context):
    context.rt_spec = ResourceTypeSpec.from_config(context.rt_config)


@when("I create a ResourceTypeSpec and call as_cli_dict")
def step_create_and_cli_dict(context):
    context.rt_spec = ResourceTypeSpec.from_config(context.rt_config)
    context.rt_cli_dict = context.rt_spec.as_cli_dict()


@when("I load the resource type via from_yaml")
def step_load_from_yaml(context):
    context.rt_schema = ResourceTypeConfigSchema.from_yaml(context.rt_yaml)


@when("I load the resource type via from_yaml_file")
def step_load_from_yaml_file(context):
    context.rt_schema = ResourceTypeConfigSchema.from_yaml_file(context.rt_yaml_path)


@when("I try to load an empty YAML string")
def step_try_load_empty_yaml(context):
    try:
        ResourceTypeConfigSchema.from_yaml("")
        context.rt_error = None
    except ValueError as exc:
        context.rt_error = str(exc)


@when("I try to load a None YAML string")
def step_try_load_none_yaml(context):
    try:
        ResourceTypeConfigSchema.from_yaml(None)
        context.rt_error = None
    except ValueError as exc:
        context.rt_error = str(exc)


@when("I try to load a YAML list")
def step_try_load_yaml_list(context):
    try:
        ResourceTypeConfigSchema.from_yaml("- item1\n- item2\n")
        context.rt_error = None
    except ValueError as exc:
        context.rt_error = str(exc)


@when("I try to load the resource type via from_yaml")
def step_try_load_from_yaml(context):
    try:
        context.rt_schema = ResourceTypeConfigSchema.from_yaml(context.rt_yaml)
        context.rt_error = None
    except (ValueError, Exception) as exc:
        context.rt_error = str(exc)


@when("I try to load from a non-existent file")
def step_try_load_nonexistent(context):
    try:
        ResourceTypeConfigSchema.from_yaml_file("/nonexistent/file.yaml")
        context.rt_error = None
    except FileNotFoundError:
        context.rt_error = "FileNotFoundError"


@when("I try to load from a directory path")
def step_try_load_directory(context):
    try:
        ResourceTypeConfigSchema.from_yaml_file(tempfile.gettempdir())
        context.rt_error = None
    except ValueError as exc:
        context.rt_error = str(exc)


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


@then("the resource type should be valid")
def step_rt_valid(context):
    assert context.rt_spec is not None


@then('the rt_spec name should be "{name}"')
def step_rt_name(context, name):
    assert context.rt_spec.name == name


@then('the resource type creation should fail with "{fragment}"')
def step_rt_creation_fail(context, fragment):
    assert context.rt_error is not None, "Expected error but got none"
    assert fragment in context.rt_error, (
        f"Expected '{fragment}' in error: {context.rt_error}"
    )


@then('the resource type resource_kind should be "{kind}"')
def step_rt_kind(context, kind):
    assert context.rt_spec.resource_kind == ResourceKind(kind)


@then('the resource type sandbox_strategy should be "{strategy}"')
def step_rt_strategy(context, strategy):
    assert context.rt_spec.sandbox_strategy == SandboxStrategy(strategy)


@then("the resource type should have {count:d} cli_arg")
def step_rt_cli_arg_count_singular(context, count):
    assert len(context.rt_spec.cli_args) == count


@then("the resource type should have {count:d} cli_args")
def step_rt_cli_arg_count(context, count):
    assert len(context.rt_spec.cli_args) == count


@then('the first cli_arg name should be "{name}"')
def step_rt_first_cli_arg(context, name):
    assert context.rt_spec.cli_args[0].name == name


@then('the resource type should have parent_types containing "{parent}"')
def step_rt_parent_types(context, parent):
    assert parent in context.rt_spec.parent_types


@then('the resource type should have child_types containing "{child}"')
def step_rt_child_types(context, child):
    assert child in context.rt_spec.child_types


@then('from_config should fail with "{fragment}"')
def step_from_config_fail(context, fragment):
    assert context.rt_error is not None, "Expected error but got none"
    assert fragment in context.rt_error, (
        f"Expected '{fragment}' in error: {context.rt_error}"
    )


@then("the resource type spec should be valid")
def step_rt_spec_valid(context):
    assert context.rt_spec is not None


@then('the rt_cli dict should contain key "{key}" with value "{value}"')
def step_cli_dict_key_value(context, key, value):
    assert key in context.rt_cli_dict
    assert str(context.rt_cli_dict[key]) == value


@then('the rt_cli dict should contain key "{key}"')
def step_cli_dict_key(context, key):
    assert key in context.rt_cli_dict


@then("the loaded schema should be valid")
def step_loaded_schema_valid(context):
    assert context.rt_schema is not None


@then('the handler should be "{handler}"')
def step_handler_value(context, handler):
    assert context.rt_schema.handler == handler


@then('the schema loader should fail with "{fragment}"')
def step_schema_loader_fail(context, fragment):
    assert context.rt_error is not None, "Expected error but got none"
    assert fragment.lower() in context.rt_error.lower(), (
        f"Expected '{fragment}' in error: {context.rt_error}"
    )


@then("the schema loader should raise FileNotFoundError")
def step_schema_loader_fnf(context):
    assert context.rt_error == "FileNotFoundError"


@then("the capabilities should have read {value}")
def step_caps_read(context, value):
    expected = value.lower() == "true"
    assert context.rt_spec.capabilities["read"] is expected


@then("the capabilities should have write {value}")
def step_caps_write(context, value):
    expected = value.lower() == "true"
    assert context.rt_spec.capabilities["write"] is expected


@then("the capabilities should have sandbox {value}")
def step_caps_sandbox(context, value):
    expected = value.lower() == "true"
    assert context.rt_spec.capabilities["sandbox"] is expected


@then("the capabilities should have checkpoint {value}")
def step_caps_checkpoint(context, value):
    expected = value.lower() == "true"
    assert context.rt_spec.capabilities["checkpoint"] is expected


@when("I try to load from a None file path")
def step_try_load_none_file_path(context):
    try:
        ResourceTypeConfigSchema.from_yaml_file(None)
        context.rt_error = None
    except ValueError as exc:
        context.rt_error = str(exc)


@then('the loaded schema should have parent type "{parent}"')
def step_loaded_schema_parent_type(context, parent):
    assert parent in context.rt_schema.parent_types


@given('a resource type YAML with sandbox_strategy "{strategy}"')
def step_yaml_with_sandbox_strategy(context, strategy):
    context.rt_yaml = f"""
name: myorg/test
resource_kind: physical
sandbox_strategy: {strategy}
"""


@then('the loaded schema sandbox_strategy should be "{strategy}"')
def step_loaded_schema_sandbox_strategy(context, strategy):
    assert context.rt_schema.sandbox_strategy == strategy, (
        f"Expected sandbox_strategy '{strategy}', got '{context.rt_schema.sandbox_strategy}'"
    )
