"""Step definitions for Temporal Data Model — metadata and node model steps."""

from __future__ import annotations

from datetime import UTC, datetime, timedelta, timezone
from typing import Any

from behave import given, then
from pydantic import ValidationError

from cleveragents.domain.models.acms.temporal import (
    TemporalMetadata,
    TemporalNode,
)

__all__: list[str] = []

# ---------------------------------------------------------------------------
# TemporalMetadata steps
# ---------------------------------------------------------------------------


@given("a TemporalMetadata with valid_from now")
def step_given_temporal_metadata_defaults(context: Any) -> None:
    context.temporal_meta = TemporalMetadata(
        valid_from=datetime.now(tz=UTC),
    )


@given(
    "a TemporalMetadata with valid_from now and valid_until tomorrow"
    ' and is_current false and is_revision_of "{revision_of}"'
)
def step_given_temporal_metadata_all_fields(
    context: Any,
    revision_of: str,
) -> None:
    now = datetime.now(tz=UTC)
    context.temporal_meta = TemporalMetadata(
        valid_from=now,
        valid_until=now + timedelta(days=1),
        is_current=False,
        is_revision_of=revision_of,
    )


@given("a TemporalMetadata with naive valid_from")
def step_given_temporal_metadata_naive_valid_from(context: Any) -> None:
    context.temporal_meta = TemporalMetadata(
        valid_from=datetime(2026, 1, 15, 10, 30, 0),
    )


@given("a TemporalMetadata with naive valid_until")
def step_given_temporal_metadata_naive_valid_until(context: Any) -> None:
    context.temporal_meta = TemporalMetadata(
        valid_from=datetime.now(tz=UTC),
        valid_until=datetime(2026, 12, 31, 23, 59, 59),
        is_current=False,
    )


@then("the temporal metadata is_current should be true")
def step_then_temporal_metadata_is_current_true(context: Any) -> None:
    assert context.temporal_meta.is_current is True


@then("the temporal metadata is_current should be false")
def step_then_temporal_metadata_is_current_false(context: Any) -> None:
    assert context.temporal_meta.is_current is False


@then("the temporal metadata valid_until should be None")
def step_then_temporal_metadata_valid_until_none(context: Any) -> None:
    assert context.temporal_meta.valid_until is None


@then("the temporal metadata valid_until should not be None")
def step_then_temporal_metadata_valid_until_not_none(context: Any) -> None:
    assert context.temporal_meta.valid_until is not None


@then("the temporal metadata is_revision_of should be None")
def step_then_temporal_metadata_revision_none(context: Any) -> None:
    assert context.temporal_meta.is_revision_of is None


@then('the temporal metadata is_revision_of should be "{expected}"')
def step_then_temporal_metadata_revision_of(
    context: Any,
    expected: str,
) -> None:
    assert context.temporal_meta.is_revision_of == expected


@then("the temporal metadata valid_from should have UTC timezone")
def step_then_temporal_metadata_valid_from_utc(context: Any) -> None:
    assert context.temporal_meta.valid_from.tzinfo is not None


@then("the temporal metadata valid_until should have UTC timezone")
def step_then_temporal_metadata_valid_until_utc(context: Any) -> None:
    assert context.temporal_meta.valid_until is not None
    assert context.temporal_meta.valid_until.tzinfo is not None


@then("modifying the temporal metadata is_current should raise an error")
def step_then_temporal_metadata_frozen(context: Any) -> None:
    try:
        context.temporal_meta.is_current = False  # type: ignore[misc]
        msg = "Expected frozen model to reject mutation"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalMetadata with empty is_revision_of should raise ValueError")
def step_then_temporal_metadata_empty_revision(context: Any) -> None:
    try:
        TemporalMetadata(
            valid_from=datetime.now(tz=UTC),
            is_revision_of="",
        )
        msg = "Expected ValueError for empty is_revision_of"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then(
    "creating a TemporalMetadata with whitespace is_revision_of should raise ValueError"
)
def step_then_temporal_metadata_whitespace_revision(context: Any) -> None:
    try:
        TemporalMetadata(
            valid_from=datetime.now(tz=UTC),
            is_revision_of="   ",
        )
        msg = "Expected ValueError for whitespace is_revision_of"
        raise AssertionError(msg)
    except ValidationError:
        pass


@given("a TemporalMetadata with non-UTC timezone")
def step_given_temporal_metadata_non_utc(context: Any) -> None:
    est = timezone(timedelta(hours=-5))
    context.temporal_meta = TemporalMetadata(
        valid_from=datetime(2026, 6, 15, 12, 0, tzinfo=est),
        is_current=True,
    )


@then(
    "creating a TemporalMetadata with valid_until before valid_from"
    " should raise ValueError",
)
def step_then_temporal_valid_until_before_valid_from(context: Any) -> None:
    try:
        now = datetime.now(tz=UTC)
        TemporalMetadata(
            valid_from=now + timedelta(hours=1),
            valid_until=now,
            is_current=False,
        )
        msg = "Expected ValueError for valid_until < valid_from"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then(
    "creating a TemporalMetadata with is_current true and valid_until set"
    " should raise ValueError",
)
def step_then_temporal_is_current_with_valid_until(context: Any) -> None:
    try:
        now = datetime.now(tz=UTC)
        TemporalMetadata(
            valid_from=now,
            valid_until=now + timedelta(hours=1),
            is_current=True,
        )
        msg = "Expected ValueError for is_current=True with valid_until"
        raise AssertionError(msg)
    except ValidationError:
        pass


# ---------------------------------------------------------------------------
# TemporalNode steps
# ---------------------------------------------------------------------------


@given(
    'a TemporalNode with uri "{uri}" and resource "{resource}"'
    ' and path "{path}" without range'
)
def step_given_temporal_node_no_range(
    context: Any,
    uri: str,
    resource: str,
    path: str,
) -> None:
    context.temporal_node = TemporalNode(
        node_uri=uri,
        source_resource=resource,
        source_path=path,
        temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
    )


@given(
    'a TemporalNode with uri "{uri}" and resource "{resource}"'
    ' and path "{path}" and range "{src_range}"'
)
def step_given_temporal_node_with_range(
    context: Any,
    uri: str,
    resource: str,
    path: str,
    src_range: str,
) -> None:
    context.temporal_node = TemporalNode(
        node_uri=uri,
        source_resource=resource,
        source_path=path,
        source_range=src_range,
        temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
    )


@then('the temporal node uri should be "{expected}"')
def step_then_temporal_node_uri(context: Any, expected: str) -> None:
    assert context.temporal_node.node_uri == expected


@then('the temporal node source_resource should be "{expected}"')
def step_then_temporal_node_resource(context: Any, expected: str) -> None:
    assert context.temporal_node.source_resource == expected


@then('the temporal node source_path should be "{expected}"')
def step_then_temporal_node_path(context: Any, expected: str) -> None:
    assert context.temporal_node.source_path == expected


@then("the temporal node source_range should be None")
def step_then_temporal_node_range_none(context: Any) -> None:
    assert context.temporal_node.source_range is None


@then('the temporal node source_range should be "{expected}"')
def step_then_temporal_node_range(context: Any, expected: str) -> None:
    assert context.temporal_node.source_range == expected


@then("the temporal node should have temporal metadata")
def step_then_temporal_node_has_metadata(context: Any) -> None:
    assert isinstance(context.temporal_node.temporal, TemporalMetadata)


@then("modifying the temporal node uri should raise an error")
def step_then_temporal_node_frozen(context: Any) -> None:
    try:
        context.temporal_node.node_uri = "changed"  # type: ignore[misc]
        msg = "Expected frozen model to reject mutation"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalNode with empty node_uri should raise ValueError")
def step_then_temporal_node_empty_uri(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="",
            source_resource="RES",
            source_path="src/a.py",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalNode with whitespace node_uri should raise ValueError")
def step_then_temporal_node_whitespace_uri(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="   ",
            source_resource="RES",
            source_path="src/a.py",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalNode with empty source_resource should raise ValueError")
def step_then_temporal_node_empty_resource(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="uko:test",
            source_resource="",
            source_path="src/a.py",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalNode with empty source_path should raise ValueError")
def step_then_temporal_node_empty_path(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="uko:test",
            source_resource="RES",
            source_path="",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalNode with empty source_range should raise ValueError")
def step_then_temporal_node_empty_range(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="uko:test",
            source_resource="RES",
            source_path="src/a.py",
            source_range="",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then("creating a TemporalNode with whitespace source_range should raise ValueError")
def step_then_temporal_node_whitespace_range(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="uko:test",
            source_resource="RES",
            source_path="src/a.py",
            source_range="   ",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValidationError:
        pass


# ---------------------------------------------------------------------------
# Round 4: string datetime coercion (NEW-3)
# ---------------------------------------------------------------------------


@given("a TemporalMetadata with valid_from as ISO-8601 string")
def step_given_metadata_string_datetime(context: Any) -> None:
    context.temporal_meta = TemporalMetadata.model_validate(
        {
            "valid_from": "2025-06-15T12:00:00+03:00",
            "is_current": True,
        }
    )


# ---------------------------------------------------------------------------
# Round 5: zero-width Unicode (NEW-14) + symmetric invariant (NEW-15)
# ---------------------------------------------------------------------------


@then(
    "creating a TemporalNode with zero-width Unicode node_uri should raise ValueError"
)
def step_then_zero_width_uri_raises(context: Any) -> None:
    try:
        TemporalNode(
            node_uri="\u200b\u200c\u200d",
            source_resource="RES001",
            source_path="src/a.py",
            temporal=TemporalMetadata(valid_from=datetime.now(tz=UTC)),
        )
        msg = "Expected ValueError for zero-width Unicode URI"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then(
    "creating a TemporalMetadata with is_current false"
    " and no valid_until should raise ValueError"
)
def step_then_historical_without_valid_until_raises(context: Any) -> None:
    try:
        TemporalMetadata(
            valid_from=datetime.now(tz=UTC),
            is_current=False,
        )
        msg = "Expected ValueError for is_current=False without valid_until"
        raise AssertionError(msg)
    except ValidationError:
        pass


@then(
    "creating a TemporalMetadata with zero-width Unicode is_revision_of"
    " should raise ValueError"
)
def step_then_zero_width_revision_of_raises(context: Any) -> None:
    try:
        TemporalMetadata(
            valid_from=datetime.now(tz=UTC),
            is_revision_of="\u200b\u200c",
        )
        msg = "Expected ValueError for zero-width Unicode is_revision_of"
        raise AssertionError(msg)
    except ValidationError:
        pass
