"""Step definitions for detail_level_coverage.feature.

These steps target specific uncovered lines in detail_level.py:
- Line 157: __setattr__ defense-in-depth re-freeze when model validator is bypassed
- Lines 166-170: __copy__ (copy.copy) support
- Line 181: __deepcopy__ with memo=None creating a fresh memo dict
- Lines 250-252: register() raising ValueError when depth exceeds max_depth
"""

import copy
import json
from types import MappingProxyType
from unittest.mock import patch

from behave import given, then, when
from pydantic import BaseModel

from cleveragents.domain.models.acms.detail_level import (
    DetailLevelMap,
)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the detail level module is imported")
def step_detail_level_module_imported(context):
    """Ensure the module is importable."""
    assert DetailLevelMap is not None


# ---------------------------------------------------------------------------
# Shared construction step
# ---------------------------------------------------------------------------
@given(
    'a DetailLevelMap with domain "{domain}" and levels {levels_json} and max_depth {max_depth:d}'
)
def step_create_detail_level_map(context, domain, levels_json, max_depth):
    """Construct a DetailLevelMap from the scenario parameters."""
    levels = json.loads(levels_json)
    context.dlm = DetailLevelMap(
        domain=domain,
        levels=levels,
        max_depth=max_depth,
    )


# ---------------------------------------------------------------------------
# Scenario: copy.copy (lines 166-170)
# ---------------------------------------------------------------------------
@when("I shallow-copy the DetailLevelMap")
def step_shallow_copy(context):
    """Perform a shallow copy via copy.copy()."""
    context.dlm_copy = copy.copy(context.dlm)


@then("the copy should be a distinct DetailLevelMap instance")
def step_verify_distinct_copy(context):
    """Verify the copy is a new object."""
    assert context.dlm_copy is not context.dlm
    assert isinstance(context.dlm_copy, DetailLevelMap)


@then("the copy should have the same domain and levels as the original")
def step_verify_copy_data(context):
    """Verify the copy has identical data."""
    assert context.dlm_copy.domain == context.dlm.domain
    assert dict(context.dlm_copy.levels) == dict(context.dlm.levels)
    assert context.dlm_copy.max_depth == context.dlm.max_depth
    # Levels should be frozen in the copy as well
    assert isinstance(context.dlm_copy.levels, MappingProxyType)


# ---------------------------------------------------------------------------
# Scenario: __deepcopy__ with None memo (line 181)
# ---------------------------------------------------------------------------
@when("I call __deepcopy__ with None memo")
def step_deepcopy_none_memo(context):
    """Call __deepcopy__ directly with memo=None to hit line 181."""
    context.dlm_deepcopy = context.dlm.__deepcopy__(None)


@then("the deep copy should be a valid DetailLevelMap with the same data")
def step_verify_deepcopy(context):
    """Verify the deep copy is valid and matches the original."""
    assert isinstance(context.dlm_deepcopy, DetailLevelMap)
    assert context.dlm_deepcopy is not context.dlm
    assert context.dlm_deepcopy.domain == context.dlm.domain
    assert dict(context.dlm_deepcopy.levels) == dict(context.dlm.levels)
    assert context.dlm_deepcopy.max_depth == context.dlm.max_depth
    assert isinstance(context.dlm_deepcopy.levels, MappingProxyType)


# ---------------------------------------------------------------------------
# Scenario: register rejects depth > max_depth (lines 250-252)
# ---------------------------------------------------------------------------
@when('I register a level "{name}" with depth {depth:d}')
def step_register_exceeding_depth(context, name, depth):
    """Attempt to register a level whose depth exceeds max_depth."""
    try:
        context.dlm.register(name, depth)
        context.register_error = None
    except ValueError as exc:
        context.register_error = exc


@then("a ValueError should be raised mentioning exceeds max_depth")
def step_verify_register_error(context):
    """Verify the ValueError was raised with the expected message."""
    assert context.register_error is not None, "Expected ValueError but none was raised"
    msg = str(context.register_error)
    assert "exceeds" in msg, f"Expected 'exceeds' in error message, got: {msg}"
    assert "max_depth" in msg, f"Expected 'max_depth' in error message, got: {msg}"


# ---------------------------------------------------------------------------
# Scenario: __setattr__ defense-in-depth re-freeze (line 157)
# ---------------------------------------------------------------------------
@when("I assign a plain dict to levels while bypassing Pydantic model validation")
def step_assign_bypassing_pydantic(context):
    """Bypass Pydantic's model validator so __setattr__ defense-in-depth kicks in.

    We mock BaseModel.__setattr__ to do a plain object.__setattr__,
    which skips Pydantic's validate_assignment (and therefore the
    _freeze_levels model validator).  The defense-in-depth check in
    DetailLevelMap.__setattr__ then detects a non-frozen dict and
    re-freezes it via line 157.
    """

    def plain_setattr(self, name, value):
        object.__setattr__(self, name, value)

    with patch.object(BaseModel, "__setattr__", plain_setattr):
        # This calls DetailLevelMap.__setattr__ which calls the mocked
        # super().__setattr__ (plain assignment), then the defense check.
        context.dlm.levels = {"X": 1, "Y": 2}


@then("levels should still be a MappingProxyType after the defense-in-depth re-freeze")
def step_verify_refreeze(context):
    """Verify that line 157 executed and re-froze the levels."""
    assert isinstance(context.dlm.levels, MappingProxyType), (
        f"Expected MappingProxyType, got {type(context.dlm.levels)}"
    )
    assert dict(context.dlm.levels) == {"X": 1, "Y": 2}
