"""Step definitions for Temporal Data Model — backend steps."""

from __future__ import annotations

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

from behave import given, then, when

from cleveragents.domain.models.acms.temporal import (
    TemporalMetadata,
    TemporalNode,
    TierRetentionConfig,
)
from cleveragents.domain.models.acms.temporal_stubs import InMemoryTemporalBackend
from cleveragents.domain.models.acms.tiers import ContextTier
from cleveragents.domain.models.core.project import TemporalScope

__all__: list[str] = []


# ---- InMemoryTemporalBackend steps ----


@given("an InMemoryTemporalBackend instance")
def step_given_inmemory_temporal_backend(context: Any) -> None:
    context.temporal_backend = InMemoryTemporalBackend()


@given('a stored temporal node "{uri}" for resource "{resource}" at "{path}"')
def step_given_stored_temporal_node(
    context: Any,
    uri: str,
    resource: str,
    path: str,
) -> None:
    now = datetime.now(tz=UTC)
    node = TemporalNode(
        node_uri=uri,
        source_resource=resource,
        source_path=path,
        temporal=TemporalMetadata(
            valid_from=now - timedelta(days=5),
            is_current=True,
        ),
    )
    context.temporal_backend.store_node(node)
    context.last_stored_node = node


@when('I get the current node for base "{base}"')
def step_when_get_current(context: Any, base: str) -> None:
    context.current_node = context.temporal_backend.get_current(base)


@then('the current node uri should be "{expected}"')
def step_then_current_node_uri(context: Any, expected: str) -> None:
    assert context.current_node is not None
    assert context.current_node.node_uri == expected


@then("the current node should be None")
def step_then_current_node_none(context: Any) -> None:
    assert context.current_node is None


@given('I create a revision from "{old}" to "{new}"')
@when('I create a revision from "{old}" to "{new}"')
def step_when_create_revision(context: Any, old: str, new: str) -> None:
    now = datetime.now(tz=UTC)
    new_node = TemporalNode(
        node_uri=new,
        source_resource="RES001",
        source_path="src/auth.py",
        temporal=TemporalMetadata(
            valid_from=now,
            is_current=True,
            is_revision_of=old,
        ),
    )
    context.new_revision = context.temporal_backend.create_revision(
        old,
        new_node,
        now,
    )


@then('the old node "{uri}" should not be current')
def step_then_old_node_not_current(context: Any, uri: str) -> None:
    node = context.temporal_backend._nodes[uri]
    assert node.temporal.is_current is False


@then('the old node "{uri}" should have valid_until set')
def step_then_old_node_valid_until(context: Any, uri: str) -> None:
    node = context.temporal_backend._nodes[uri]
    assert node.temporal.valid_until is not None


@then('the new node "{uri}" should be current')
def step_then_new_node_current(context: Any, uri: str) -> None:
    node = context.temporal_backend._nodes[uri]
    assert node.temporal.is_current is True


@then('the new node "{uri}" should have is_revision_of "{expected}"')
def step_then_new_node_revision_of(
    context: Any,
    uri: str,
    expected: str,
) -> None:
    node = context.temporal_backend._nodes[uri]
    assert node.temporal.is_revision_of == expected


@then('creating a revision from nonexistent "{uri}" should raise KeyError')
def step_then_revision_nonexistent(context: Any, uri: str) -> None:
    now = datetime.now(tz=UTC)
    new_node = TemporalNode(
        node_uri="uko:new",
        source_resource="RES",
        source_path="src/a.py",
        temporal=TemporalMetadata(valid_from=now, is_current=True),
    )
    try:
        context.temporal_backend.create_revision(uri, new_node, now)
        msg = "Expected KeyError"
        raise AssertionError(msg)
    except KeyError:
        pass


@then("creating a revision from blank URI should raise ValueError")
def step_then_revision_blank_uri(context: Any) -> None:
    now = datetime.now(tz=UTC)
    new_node = TemporalNode(
        node_uri="uko:new",
        source_resource="RES",
        source_path="src/a.py",
        temporal=TemporalMetadata(valid_from=now, is_current=True),
    )
    try:
        context.temporal_backend.create_revision("", new_node, now)
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


@then('creating a revision from "{uri}" to itself should raise ValueError')
def step_then_revision_same_uri(context: Any, uri: str) -> None:
    now = datetime.now(tz=UTC)
    same_uri_node = TemporalNode(
        node_uri=uri,
        source_resource="RES",
        source_path="src/a.py",
        temporal=TemporalMetadata(valid_from=now, is_current=True),
    )
    try:
        context.temporal_backend.create_revision(uri, same_uri_node, now)
        msg = "Expected ValueError for same-URI revision"
        raise AssertionError(msg)
    except ValueError:
        pass


@then('storing a duplicate node "{uri}" should raise ValueError')
def step_then_store_duplicate_raises(context: Any, uri: str) -> None:
    now = datetime.now(tz=UTC)
    node = TemporalNode(
        node_uri=uri,
        source_resource="RES_DUP",
        source_path="src/dup.py",
        temporal=TemporalMetadata(valid_from=now, is_current=True),
    )
    try:
        context.temporal_backend.store_node(node)
        msg = "Expected ValueError for duplicate store_node"
        raise AssertionError(msg)
    except ValueError:
        pass


# ---- get_history steps ----


@when('I get the history for base "{base}" with scope ALL')
def step_when_get_history_all(context: Any, base: str) -> None:
    context.history = context.temporal_backend.get_history(
        base,
        TemporalScope.ALL,
    )


@when('I get the history for base "{base}" with scope CURRENT')
def step_when_get_history_current(context: Any, base: str) -> None:
    context.history = context.temporal_backend.get_history(
        base,
        TemporalScope.CURRENT,
    )


@then("the history should have {count:d} nodes")
def step_then_history_count(context: Any, count: int) -> None:
    assert len(context.history) == count


@then("the history should be ordered newest first")
def step_then_history_ordered(context: Any) -> None:
    for i in range(len(context.history) - 1):
        assert (
            context.history[i].temporal.valid_from
            >= context.history[i + 1].temporal.valid_from
        )


@then("getting history for blank base URI should raise ValueError")
def step_then_history_blank_uri(context: Any) -> None:
    try:
        context.temporal_backend.get_history("", TemporalScope.ALL)
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


# ---- revision chain steps ----


@when('I get the revision chain for "{uri}"')
def step_when_get_chain(context: Any, uri: str) -> None:
    context.chain = context.temporal_backend.get_revision_chain(uri)


@then("the revision chain should have depth {expected:d}")
def step_then_chain_depth_backend(context: Any, expected: int) -> None:
    assert context.chain.depth == expected


@then('the revision chain current should be "{expected}"')
def step_then_chain_current(context: Any, expected: str) -> None:
    assert context.chain.current_uri == expected


@then('the revision chain predecessors should include "{uri}"')
def step_then_chain_preds_include(context: Any, uri: str) -> None:
    assert uri in context.chain.predecessors


@then("getting revision chain for nonexistent URI should raise KeyError")
def step_then_chain_nonexistent(context: Any) -> None:
    try:
        context.temporal_backend.get_revision_chain("uko:nonexistent")
        msg = "Expected KeyError"
        raise AssertionError(msg)
    except KeyError:
        pass


# ---- query by tier steps ----


@when("I query the HOT tier")
def step_when_query_hot(context: Any) -> None:
    retention = TierRetentionConfig()
    context.tier_query = context.temporal_backend.query_by_tier(
        ContextTier.HOT,
        TemporalScope.CURRENT,
        retention,
    )


@when("I query the COLD tier")
def step_when_query_cold(context: Any) -> None:
    retention = TierRetentionConfig()
    context.tier_query = context.temporal_backend.query_by_tier(
        ContextTier.COLD,
        TemporalScope.ALL,
        retention,
    )


@then("the tier query should return {count:d} node")
def step_then_tier_query_count_singular(context: Any, count: int) -> None:
    assert len(context.tier_query.nodes) == count


@then("the tier query should return {count:d} nodes")
def step_then_tier_query_count(context: Any, count: int) -> None:
    assert len(context.tier_query.nodes) == count


@then("the tier query node should be current")
def step_then_tier_query_node_current(context: Any) -> None:
    for node in context.tier_query.nodes:
        assert node.temporal.is_current is True


# ---- mark historical steps ----


@given('I mark "{uri}" as historical')
@when('I mark "{uri}" as historical')
def step_when_mark_historical(context: Any, uri: str) -> None:
    now = datetime.now(tz=UTC)
    context.marked_node = context.temporal_backend.mark_historical(
        uri,
        now,
    )


@then("the marked node should not be current")
def step_then_marked_not_current(context: Any) -> None:
    assert context.marked_node.temporal.is_current is False


@then("the marked node should have valid_until set")
def step_then_marked_valid_until(context: Any) -> None:
    assert context.marked_node.temporal.valid_until is not None


@then("marking blank URI as historical should raise ValueError")
def step_then_mark_blank_uri(context: Any) -> None:
    try:
        context.temporal_backend.mark_historical(
            "",
            datetime.now(tz=UTC),
        )
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


@then("marking nonexistent URI as historical should raise KeyError")
def step_then_mark_nonexistent(context: Any) -> None:
    try:
        context.temporal_backend.mark_historical(
            "uko:nonexistent",
            datetime.now(tz=UTC),
        )
        msg = "Expected KeyError"
        raise AssertionError(msg)
    except KeyError:
        pass


# ---- Edge-case guards (Round 3 bug-hunt fixes) ----


@then(
    'marking already-historical "{uri}" should raise ValueError',
)
def step_then_mark_already_historical(context: Any, uri: str) -> None:
    try:
        context.temporal_backend.mark_historical(uri, datetime.now(tz=UTC))
        msg = "Expected ValueError for already-historical node"
        raise AssertionError(msg)
    except ValueError:
        pass


@then(
    'creating a revision from historical "{current}" '
    'to "{new_uri}" should raise ValueError',
)
def step_then_revise_historical_raises(
    context: Any,
    current: str,
    new_uri: str,
) -> None:
    now = datetime.now(tz=UTC)
    new_node = TemporalNode(
        node_uri=new_uri,
        source_resource="RES001",
        source_path="src/auth.py",
        temporal=TemporalMetadata(
            valid_from=now,
            is_current=True,
            is_revision_of=current,
        ),
    )
    try:
        context.temporal_backend.create_revision(current, new_node, now)
        msg = "Expected ValueError for revising historical node"
        raise AssertionError(msg)
    except ValueError:
        pass


@then('the revision chain current_uri should be "{expected}"')
def step_then_chain_current_uri(context: Any, expected: str) -> None:
    assert context.chain.current_uri == expected


@then('the revision chain predecessors should contain "{expected}"')
def step_then_chain_predecessors_contain(context: Any, expected: str) -> None:
    assert expected in context.chain.predecessors


@given("a stored temporal node with branching revision chain")
def step_given_branching_chain(context: Any) -> None:
    """Create A → B and A → C (sibling branches) via _nodes."""
    now = datetime.now(tz=UTC)
    base = "uko-py:class/Branch"
    nodes = context.temporal_backend._nodes
    for suffix, meta_kw in [
        (
            "_A",
            {
                "valid_from": now - timedelta(hours=2),
                "valid_until": now - timedelta(hours=1),
                "is_current": False,
            },
        ),
        (
            "_B",
            {
                "valid_from": now - timedelta(hours=1),
                "is_current": True,
                "is_revision_of": f"{base}_A",
            },
        ),
        ("_C", {"valid_from": now, "is_current": True, "is_revision_of": f"{base}_A"}),
    ]:
        uri = f"{base}{suffix}"
        nodes[uri] = TemporalNode(
            node_uri=uri,
            source_resource="RES001",
            source_path="src/a.py",
            temporal=TemporalMetadata(**meta_kw),
        )


@then('the revision chain should not contain "{uri}"')
def step_then_chain_not_contain(context: Any, uri: str) -> None:
    assert uri not in context.chain.all_uris, (
        f"{uri} should not be in chain {context.chain.all_uris}"
    )


# ---- Diff-coverage steps (RECENT scope, cycle guard, WARM tier) ----


@when('I get the history for base "{base}" with scope RECENT')
def step_when_get_history_recent(context: Any, base: str) -> None:
    context.history = context.temporal_backend.get_history(
        base,
        TemporalScope.RECENT,
    )


@given("a stored temporal node with cyclic revision chain")
def step_given_cyclic_revision_chain(context: Any) -> None:
    """Create two nodes whose is_revision_of pointers form a cycle."""
    now = datetime.now(tz=UTC)
    for uri, meta_kw in [
        (
            "uko-py:class/Cycle_A",
            {
                "valid_from": now - timedelta(days=2),
                "valid_until": now - timedelta(days=1),
                "is_current": False,
                "is_revision_of": "uko-py:class/Cycle_B",
            },
        ),
        (
            "uko-py:class/Cycle_B",
            {
                "valid_from": now,
                "is_current": True,
                "is_revision_of": "uko-py:class/Cycle_A",
            },
        ),
    ]:
        context.temporal_backend.store_node(
            TemporalNode(
                node_uri=uri,
                source_resource="RES001",
                source_path="src/cycle.py",
                temporal=TemporalMetadata(**meta_kw),
            )
        )


@given("a stored temporal node with dangling predecessor")
def step_given_dangling_predecessor(context: Any) -> None:
    context.temporal_backend.store_node(
        TemporalNode(
            node_uri="uko-py:class/Dangling_B",
            source_resource="RES001",
            source_path="src/dangling.py",
            temporal=TemporalMetadata(
                valid_from=datetime.now(tz=UTC),
                is_current=True,
                is_revision_of="uko-py:class/Dangling_A",
            ),
        )
    )
