"""Step definitions for Resource Registry domain model tests."""

from __future__ import annotations

import time

from behave import given, then, when
from behave.runner import Context
from pydantic import ValidationError

from cleveragents.domain.models.core.resource import (
    PhysVirt,
    Resource,
    ResourceCapabilities,
    SandboxStrategy,
)

VALID_ULID = "01HZQX7K8M3N4P5R6S7T8V9W0X"


# -- PhysVirt enum -----------------------------------------------------------


@then('the PhysVirt enum should have values "{values}"')
def step_physvirt_values(context: Context, values: str) -> None:
    """Check PhysVirt enum has exactly the expected values."""
    expected = {v.strip() for v in values.split(",")}
    actual = {member.value for member in PhysVirt}
    assert actual == expected, f"Expected {expected}, got {actual}"


@given('a PhysVirt value of "{value}"')
def step_create_physvirt(context: Context, value: str) -> None:
    """Create a PhysVirt enum value."""
    context.physvirt = PhysVirt(value)


@then('the PhysVirt string value should be "{expected}"')
def step_check_physvirt_value(context: Context, expected: str) -> None:
    """Check PhysVirt string value."""
    assert str(context.physvirt) == expected, (
        f"Expected '{expected}', got '{context.physvirt}'"
    )


@when('I try to create a PhysVirt with value "{value}"')
def step_try_create_physvirt(context: Context, value: str) -> None:
    """Try to create a PhysVirt enum value, expecting failure."""
    try:
        PhysVirt(value)
        context.resource_error = None
    except ValueError as e:
        context.resource_error = e


@then("a resource validation error should be raised")
def step_check_resource_error(context: Context) -> None:
    """Check that a validation error was raised."""
    assert context.resource_error is not None, (
        "Expected a validation error but none was raised"
    )


# -- SandboxStrategy enum ---------------------------------------------------


@then('the SandboxStrategy enum should have values "{values}"')
def step_sandbox_strategy_values(context: Context, values: str) -> None:
    """Check SandboxStrategy enum has exactly the expected values."""
    expected = {v.strip() for v in values.split(",")}
    actual = {member.value for member in SandboxStrategy}
    assert actual == expected, f"Expected {expected}, got {actual}"


@given('a SandboxStrategy value of "{value}"')
def step_create_sandbox_strategy(context: Context, value: str) -> None:
    """Create a SandboxStrategy enum value."""
    context.sandbox_strategy = SandboxStrategy(value)


@then('the SandboxStrategy string value should be "{expected}"')
def step_check_sandbox_strategy_value(context: Context, expected: str) -> None:
    """Check SandboxStrategy string value."""
    assert str(context.sandbox_strategy) == expected, (
        f"Expected '{expected}', got '{context.sandbox_strategy}'"
    )


@when('I try to create a SandboxStrategy with value "{value}"')
def step_try_create_sandbox_strategy(context: Context, value: str) -> None:
    """Try to create a SandboxStrategy, expecting failure."""
    try:
        SandboxStrategy(value)
        context.resource_error = None
    except ValueError as e:
        context.resource_error = e


# -- ResourceCapabilities ---------------------------------------------------


@given("default ResourceCapabilities")
def step_default_capabilities(context: Context) -> None:
    """Create default ResourceCapabilities."""
    context.capabilities = ResourceCapabilities()


@given("ResourceCapabilities with readable false and checkpointable true")
def step_custom_capabilities(context: Context) -> None:
    """Create ResourceCapabilities with custom values."""
    context.capabilities = ResourceCapabilities(readable=False, checkpointable=True)


@then("the capability {field} should be {expected}")
def step_check_capability(context: Context, field: str, expected: str) -> None:
    """Check a capability field value."""
    actual = getattr(context.capabilities, field)
    expected_bool = expected.lower() == "true"
    assert actual == expected_bool, f"Expected {field}={expected_bool}, got {actual}"


@when("I try to mutate a ResourceCapabilities field")
def step_try_mutate_capabilities(context: Context) -> None:
    """Try to mutate a frozen ResourceCapabilities."""
    try:
        context.capabilities.readable = False  # type: ignore[misc]
        context.resource_error = None
    except ValidationError as e:
        context.resource_error = e


# -- Resource model - creation -----------------------------------------------


@given(
    'a Resource with resource_id "{rid}" type "{rtype}"'
    ' and classification "{classification}"'
)
def step_create_minimal_resource(
    context: Context, rid: str, rtype: str, classification: str
) -> None:
    """Create a minimal Resource."""
    context.resource = Resource(
        resource_id=rid,
        resource_type_name=rtype,
        classification=PhysVirt(classification),
    )


@given(
    'a full Resource with id "{rid}" name "{name}" type "{rtype}"'
    ' classification "{classification}" description "{desc}"'
)
def step_create_full_resource(
    context: Context,
    rid: str,
    name: str,
    rtype: str,
    classification: str,
    desc: str,
) -> None:
    """Create a full Resource with all fields."""
    context.resource = Resource(
        resource_id=rid,
        name=name,
        resource_type_name=rtype,
        classification=PhysVirt(classification),
        description=desc,
    )


@given('a Resource with sandbox strategy "{strategy}"')
def step_create_resource_with_sandbox(context: Context, strategy: str) -> None:
    """Create a Resource with a sandbox strategy override."""
    context.resource = Resource(
        resource_id=VALID_ULID,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        sandbox_strategy=SandboxStrategy(strategy),
    )


@given("a Resource with sandboxable false and checkpointable true")
def step_create_resource_with_capabilities(context: Context) -> None:
    """Create a Resource with custom capabilities."""
    context.resource = Resource(
        resource_id=VALID_ULID,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        capabilities=ResourceCapabilities(sandboxable=False, checkpointable=True),
    )


@given("a Resource with writable false")
def step_create_readonly_resource(context: Context) -> None:
    """Create a Resource with writable=false."""
    context.resource = Resource(
        resource_id=VALID_ULID,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        capabilities=ResourceCapabilities(writable=False),
    )


@given('a Resource with content_hash "{hash_val}"')
def step_create_resource_with_hash(context: Context, hash_val: str) -> None:
    """Create a Resource with content_hash."""
    context.resource = Resource(
        resource_id=VALID_ULID,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        content_hash=hash_val,
    )


@given('a Resource with location "{location}"')
def step_create_resource_with_location(context: Context, location: str) -> None:
    """Create a Resource with location."""
    context.resource = Resource(
        resource_id=VALID_ULID,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        location=location,
    )


@given("a Resource with parents and children")
def step_create_resource_with_dag(context: Context) -> None:
    """Create a Resource with parent/child references."""
    parent_id = "01HZQX7K8M3N4P5R6S7T8V9W01"
    child1_id = "01HZQX7K8M3N4P5R6S7T8V9W02"
    child2_id = "01HZQX7K8M3N4P5R6S7T8V9W03"
    context.resource = Resource(
        resource_id=VALID_ULID,
        resource_type_name="git-checkout",
        classification=PhysVirt.PHYSICAL,
        parents=[parent_id],
        children=[child1_id, child2_id],
    )


# -- Resource model - assertion steps ----------------------------------------


@then('the resource resource_id should be "{expected}"')
def step_check_resource_id(context: Context, expected: str) -> None:
    """Check resource_id."""
    assert context.resource.resource_id == expected


@then('the resource type name should be "{expected}"')
def step_check_resource_type_name(context: Context, expected: str) -> None:
    """Check resource_type_name."""
    assert context.resource.resource_type_name == expected


@then('the resource classification should be "{expected}"')
def step_check_classification(context: Context, expected: str) -> None:
    """Check classification."""
    assert context.resource.classification == expected


@then("the resource name should be empty")
def step_check_name_empty(context: Context) -> None:
    """Check name is None."""
    assert context.resource.name is None


@then('the resource name should be "{expected}"')
def step_check_resource_name(context: Context, expected: str) -> None:
    """Check resource name."""
    assert context.resource.name == expected


@then("the resource description should be empty")
def step_check_description_empty(context: Context) -> None:
    """Check description is None."""
    assert context.resource.description is None


@then('the resource description should be "{expected}"')
def step_check_resource_description(context: Context, expected: str) -> None:
    """Check description."""
    assert context.resource.description == expected


@then("the resource properties should be empty")
def step_check_properties_empty(context: Context) -> None:
    """Check properties dict is empty."""
    assert context.resource.properties == {}


@then("the resource parents should be empty")
def step_check_parents_empty(context: Context) -> None:
    """Check parents list is empty."""
    assert context.resource.parents == []


@then("the resource children should be empty")
def step_check_children_empty(context: Context) -> None:
    """Check children list is empty."""
    assert context.resource.children == []


@then("the resource linked_projects should be empty")
def step_check_linked_projects_empty(context: Context) -> None:
    """Check linked_projects list is empty."""
    assert context.resource.linked_projects == []


@then("the resource created_at should be set")
def step_check_created_at(context: Context) -> None:
    """Check created_at is not None."""
    assert context.resource.created_at is not None


@then("the resource updated_at should be set")
def step_check_updated_at(context: Context) -> None:
    """Check updated_at is not None."""
    assert context.resource.updated_at is not None


@then('the resource sandbox strategy should be "{expected}"')
def step_check_sandbox_strategy(context: Context, expected: str) -> None:
    """Check sandbox_strategy."""
    assert context.resource.sandbox_strategy == expected


@then("the resource can_sandbox should be {expected}")
def step_check_can_sandbox(context: Context, expected: str) -> None:
    """Check can_sandbox property."""
    expected_bool = expected.lower() == "true"
    assert context.resource.can_sandbox == expected_bool


@then("the resource can_checkpoint should be {expected}")
def step_check_can_checkpoint(context: Context, expected: str) -> None:
    """Check can_checkpoint property."""
    expected_bool = expected.lower() == "true"
    assert context.resource.can_checkpoint == expected_bool


@then("the resource is_physical should be {expected}")
def step_check_is_physical(context: Context, expected: str) -> None:
    """Check is_physical property."""
    expected_bool = expected.lower() == "true"
    assert context.resource.is_physical == expected_bool


@then("the resource is_virtual should be {expected}")
def step_check_is_virtual(context: Context, expected: str) -> None:
    """Check is_virtual property."""
    expected_bool = expected.lower() == "true"
    assert context.resource.is_virtual == expected_bool


@then("the resource is_read_only should be {expected}")
def step_check_is_read_only(context: Context, expected: str) -> None:
    """Check is_read_only property."""
    expected_bool = expected.lower() == "true"
    assert context.resource.is_read_only == expected_bool


@then('the resource content_hash should be "{expected}"')
def step_check_content_hash(context: Context, expected: str) -> None:
    """Check content_hash."""
    assert context.resource.content_hash == expected


@then('the resource location should be "{expected}"')
def step_check_location(context: Context, expected: str) -> None:
    """Check location."""
    assert context.resource.location == expected


@then("the resource should have {count:d} parent")
def step_check_parent_count(context: Context, count: int) -> None:
    """Check parent count."""
    assert len(context.resource.parents) == count


@then("the resource should have {count:d} children")
def step_check_child_count(context: Context, count: int) -> None:
    """Check children count."""
    assert len(context.resource.children) == count


# -- Resource model - validation failures ------------------------------------


@when('I try to create a Resource with resource_id "{rid}"')
def step_try_create_resource_bad_ulid(context: Context, rid: str) -> None:
    """Try to create a Resource with invalid ULID."""
    try:
        Resource(
            resource_id=rid,
            resource_type_name="git-checkout",
            classification=PhysVirt.PHYSICAL,
        )
        context.resource_error = None
    except ValidationError as e:
        context.resource_error = e


@when("I try to create a Resource with empty resource_type_name")
def step_try_create_resource_empty_type(context: Context) -> None:
    """Try to create a Resource with empty type name."""
    try:
        Resource(
            resource_id=VALID_ULID,
            resource_type_name="",
            classification=PhysVirt.PHYSICAL,
        )
        context.resource_error = None
    except ValidationError as e:
        context.resource_error = e


@when("I try to create a Resource with empty string name")
def step_try_create_resource_empty_name(context: Context) -> None:
    """Try to create a Resource with empty string name."""
    try:
        Resource(
            resource_id=VALID_ULID,
            name="",
            resource_type_name="git-checkout",
            classification=PhysVirt.PHYSICAL,
        )
        context.resource_error = None
    except ValidationError as e:
        context.resource_error = e


@when("I try to create a Resource with empty location")
def step_try_create_resource_empty_location(context: Context) -> None:
    """Try to create a Resource with empty location."""
    try:
        Resource(
            resource_id=VALID_ULID,
            resource_type_name="git-checkout",
            classification=PhysVirt.PHYSICAL,
            location="",
        )
        context.resource_error = None
    except ValidationError as e:
        context.resource_error = e


# -- Resource model - frozen -------------------------------------------------


@when("I try to mutate a Resource field")
def step_try_mutate_resource(context: Context) -> None:
    """Try to mutate a frozen Resource."""
    try:
        context.resource.name = "new-name"  # type: ignore[misc]
        context.resource_error = None
    except ValidationError as e:
        context.resource_error = e


# -- Resource model - with_updated_at ---------------------------------------


@when("I call with_updated_at on the resource")
def step_call_with_updated_at(context: Context) -> None:
    """Call with_updated_at and store old/new resources."""
    context.old_resource = context.resource
    time.sleep(0.01)  # Ensure timestamp differs
    context.new_resource = context.resource.with_updated_at()


@then("the new resource should have a different updated_at")
def step_check_different_updated_at(context: Context) -> None:
    """Check updated_at changed."""
    assert context.new_resource.updated_at != context.old_resource.updated_at


@then("the new resource should have the same resource_id")
def step_check_same_resource_id(context: Context) -> None:
    """Check resource_id unchanged."""
    assert context.new_resource.resource_id == context.old_resource.resource_id
