"""Step definitions for sandbox_strategy_registry_coverage.feature.

These steps target specific uncovered lines in strategy_registry.py:
- Lines 65-68: CustomStrategyConfig.__init__ assignment block
- Line 167: register_from_config delegation
- Lines 196-200: register_all_from_config skip on missing module/class
- Lines 205-209: register_all_from_config error handling on load failure
"""

import logging
from unittest.mock import MagicMock

from behave import given, then, when

from cleveragents.infrastructure.plugins.exceptions import (
    PluginLoadError,
)
from cleveragents.infrastructure.sandbox.strategy_registry import (
    CustomStrategyConfig,
    SandboxStrategyRegistry,
)

# ---------------------------------------------------------------------------
# Helper: a class that satisfies _validate_protocol (has all 9 methods)
# ---------------------------------------------------------------------------


class _CompliantStrategy:
    """Dummy class with all 9 required SandboxStrategyProtocol methods."""

    def create(self): ...
    def read(self): ...
    def write(self): ...
    def diff(self): ...
    def commit(self): ...
    def rollback(self): ...
    def checkpoint(self): ...
    def restore_checkpoint(self): ...
    def cleanup(self): ...


class _NonCompliantStrategy:
    """Dummy class missing required methods."""

    def create(self): ...


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("the sandbox strategy registry module is imported")
def step_module_imported(context):
    """Verify the module is importable."""
    assert CustomStrategyConfig is not None
    assert SandboxStrategyRegistry is not None


# ---------------------------------------------------------------------------
# CustomStrategyConfig success (lines 65-68)
# ---------------------------------------------------------------------------


@when(
    'I create a CustomStrategyConfig with name "{name}" module "{module}" and class "{cls}"'
)
def step_create_config(context, name, module, cls):
    """Create a CustomStrategyConfig with the given values."""
    context.strategy_config = CustomStrategyConfig(
        name=name,
        module=module,
        class_name=cls,
    )


@then('the config should have name "{name}" module "{module}" and class_name "{cls}"')
def step_verify_config_fields(context, name, module, cls):
    """Verify the config fields were assigned correctly."""
    cfg = context.strategy_config
    assert cfg.name == name, f"Expected name={name!r}, got {cfg.name!r}"
    assert cfg.module == module, f"Expected module={module!r}, got {cfg.module!r}"
    assert cfg.class_name == cls, f"Expected class_name={cls!r}, got {cfg.class_name!r}"


@then("the config options should default to an empty dict")
def step_verify_default_options(context):
    """Verify options defaults to empty dict when not provided."""
    assert context.strategy_config.options == {}, (
        f"Expected empty dict, got {context.strategy_config.options!r}"
    )


@when(
    'I create a CustomStrategyConfig with name "{name}" module "{module}" class "{cls}" and options'
)
def step_create_config_with_options(context, name, module, cls):
    """Create a CustomStrategyConfig with explicit options."""
    context.strategy_config = CustomStrategyConfig(
        name=name,
        module=module,
        class_name=cls,
        options={"timeout": 30, "retries": 5},
    )


@then("the config options should contain the provided values")
def step_verify_explicit_options(context):
    """Verify options stores the provided dict."""
    opts = context.strategy_config.options
    assert opts == {"timeout": 30, "retries": 5}, f"Unexpected options: {opts!r}"


# ---------------------------------------------------------------------------
# Registry with mock loader helpers
# ---------------------------------------------------------------------------


@given("a registry with a mock loader that returns a compliant class")
def step_registry_with_mock_loader(context):
    """Create a SandboxStrategyRegistry with a mocked PluginLoader."""
    mock_loader = MagicMock()
    mock_loader.load_class.return_value = _CompliantStrategy
    context.registry = SandboxStrategyRegistry(loader=mock_loader)
    context.mock_loader = mock_loader


@given('a CustomStrategyConfig named "{name}" with module "{module}" and class "{cls}"')
def step_create_named_config(context, name, module, cls):
    """Create and store a CustomStrategyConfig for later use."""
    context.strategy_config = CustomStrategyConfig(
        name=name,
        module=module,
        class_name=cls,
    )


# ---------------------------------------------------------------------------
# register_from_config (line 167)
# ---------------------------------------------------------------------------


@when("I call register_from_config with that config")
def step_call_register_from_config(context):
    """Call register_from_config using the stored config."""
    context.returned_cls = context.registry.register_from_config(
        context.strategy_config,
    )


@then('the strategy "{name}" should be registered in the registry')
def step_verify_strategy_registered(context, name):
    """Verify the strategy was successfully registered."""
    assert context.registry.has(name), (
        f"Strategy {name!r} not found; registered: {context.registry.list_strategies()}"
    )
    assert context.returned_cls is _CompliantStrategy


# ---------------------------------------------------------------------------
# register_all_from_config: skip missing module (lines 196-200)
# ---------------------------------------------------------------------------


@when("I call register_all_from_config with an entry that has an empty module")
def step_register_all_missing_module(context):
    """Call register_all_from_config with a config missing the module key."""
    context.log_handler = _CapturingLogHandler()
    logger = logging.getLogger("cleveragents.infrastructure.sandbox.strategy_registry")
    # Re-enable the logger in case Alembic's fileConfig() disabled it
    logger.disabled = False
    logger.addHandler(context.log_handler)
    logger.setLevel(logging.DEBUG)

    def cleanup():
        logger.removeHandler(context.log_handler)

    context.add_cleanup(cleanup)

    configs = {
        "bad_no_module": {
            "module": "",
            "class": "SomeClass",
        },
    }
    context.registered_names = context.registry.register_all_from_config(configs)
    context.skipped_entry_name = "bad_no_module"


@then("the returned list should not contain the skipped entry name")
def step_verify_skipped_not_in_list(context):
    """Verify the skipped entry is not in the returned list."""
    assert context.skipped_entry_name not in context.registered_names, (
        f"Expected {context.skipped_entry_name!r} to be absent, "
        f"got {context.registered_names}"
    )


@then("a warning should be logged about the skipped entry")
def step_verify_warning_logged(context):
    """Verify a warning was logged for the skipped entry."""
    warnings = [r for r in context.log_handler.records if r.levelno == logging.WARNING]
    assert any("Skipping custom strategy" in r.getMessage() for r in warnings), (
        f"Expected a 'Skipping custom strategy' warning, "
        f"got: {[r.getMessage() for r in warnings]}"
    )


@when("I call register_all_from_config with an entry that has an empty class")
def step_register_all_missing_class(context):
    """Call register_all_from_config with a config missing the class key."""
    configs = {
        "bad_no_class": {
            "module": "some.module",
            "class": "",
        },
    }
    context.registered_names = context.registry.register_all_from_config(configs)
    context.skipped_entry_name = "bad_no_class"


# ---------------------------------------------------------------------------
# register_all_from_config: PluginLoadError (lines 205-209)
# ---------------------------------------------------------------------------


@given("a registry whose loader raises PluginLoadError")
def step_registry_loader_raises_load_error(context):
    """Create a registry whose loader always raises PluginLoadError."""
    mock_loader = MagicMock()
    mock_loader.load_class.side_effect = PluginLoadError("boom")
    context.registry = SandboxStrategyRegistry(loader=mock_loader)

    # Attach a log handler to capture error logs
    context.log_handler = _CapturingLogHandler()
    logger = logging.getLogger("cleveragents.infrastructure.sandbox.strategy_registry")
    # Re-enable the logger in case Alembic's fileConfig() disabled it
    logger.disabled = False
    logger.addHandler(context.log_handler)
    logger.setLevel(logging.DEBUG)

    def cleanup():
        logger.removeHandler(context.log_handler)

    context.add_cleanup(cleanup)


@when("I call register_all_from_config with an entry that will fail to load")
def step_register_all_fail_load(context):
    """Call register_all_from_config with a valid-looking config that will fail."""
    configs = {
        "failing_strategy": {
            "module": "nonexistent.module",
            "class": "NoSuchClass",
        },
    }
    context.registered_names = context.registry.register_all_from_config(configs)


@then("the returned list should be empty")
def step_verify_empty_result(context):
    """Verify no strategies were registered."""
    assert context.registered_names == [], (
        f"Expected empty list, got {context.registered_names!r}"
    )


@then("an error should be logged about the failed registration")
def step_verify_error_logged(context):
    """Verify an error was logged for the failed registration."""
    errors = [r for r in context.log_handler.records if r.levelno == logging.ERROR]
    assert any(
        "Failed to register custom strategy" in r.getMessage() for r in errors
    ), f"Expected a 'Failed to register' error, got: {[r.getMessage() for r in errors]}"


# ---------------------------------------------------------------------------
# register_all_from_config: ProtocolMismatchError (lines 205-209)
# ---------------------------------------------------------------------------


@given("a registry whose loader returns a non-compliant class")
def step_registry_loader_returns_bad_class(context):
    """Create a registry whose loader returns a class that fails protocol check."""
    mock_loader = MagicMock()
    mock_loader.load_class.return_value = _NonCompliantStrategy
    context.registry = SandboxStrategyRegistry(loader=mock_loader)


@when("I call register_all_from_config with an entry that will fail protocol check")
def step_register_all_fail_protocol(context):
    """Call register_all_from_config; the protocol validation will fail."""
    configs = {
        "bad_protocol": {
            "module": "some.module",
            "class": "BadClass",
        },
    }
    context.registered_names = context.registry.register_all_from_config(configs)


# ---------------------------------------------------------------------------
# Internal helper: capturing log handler
# ---------------------------------------------------------------------------


class _CapturingLogHandler(logging.Handler):
    """A logging handler that captures LogRecords for assertion."""

    def __init__(self):
        super().__init__()
        self.records: list[logging.LogRecord] = []

    def emit(self, record: logging.LogRecord) -> None:
        self.records.append(record)
