"""Step definitions for features/unified_context_models.feature.

Tests isinstance compatibility and construction of the unified
ContextFragment model hierarchy.
"""

from __future__ import annotations

from datetime import UTC, datetime, timedelta

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

from cleveragents.domain.models.acms.crp import (
    AssembledContext as CRPAssembledContext,
)
from cleveragents.domain.models.acms.crp import (
    ContextBudget as CRPContextBudget,
)
from cleveragents.domain.models.acms.crp import (
    ContextFragment as CRPContextFragment,
)
from cleveragents.domain.models.acms.crp import (
    FragmentProvenance as CRPFragmentProvenance,
)
from cleveragents.domain.models.core.context_fragment import (
    ContextBudget,
    ContextFragment,
    ContextPayload,
    FragmentProvenance,
)

# Default provenance for test fragments.
_DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://unified")


# ---------------------------------------------------------------------------
# Given — Module availability
# ---------------------------------------------------------------------------


@given("the unified model modules are available")
def step_modules_available(context: Context) -> None:
    pass


# ---------------------------------------------------------------------------
# Given — FragmentProvenance
# ---------------------------------------------------------------------------


@given('a core FragmentProvenance with resource_uri "{uri}"')
def step_core_provenance(context: Context, uri: str) -> None:
    context.provenance = FragmentProvenance(resource_uri=uri)


@given('a core FragmentProvenance for strategy "{strategy}" with resource_uri "{uri}"')
def step_core_provenance_with_strategy(
    context: Context, strategy: str, uri: str
) -> None:
    context.provenance = FragmentProvenance(resource_uri=uri, strategy=strategy)


@given('a CRP FragmentProvenance with resource_uri "{uri}"')
def step_crp_provenance(context: Context, uri: str) -> None:
    context.crp_provenance = CRPFragmentProvenance(resource_uri=uri)


# ---------------------------------------------------------------------------
# Given — ContextFragment
# ---------------------------------------------------------------------------


@given('a core ContextFragment with uko_node "{uko_node}" and content "{content}"')
def step_core_fragment(context: Context, uko_node: str, content: str) -> None:
    context.unified_fragment = ContextFragment(
        uko_node=uko_node,
        content=content,
        token_count=len(content),
        provenance=_DEFAULT_PROVENANCE,
    )


@given("two core ContextFragments with identical fields")
def step_two_identical_fragments(context: Context) -> None:
    fixed_time = datetime.now(UTC)
    frag_a = ContextFragment(
        fragment_id="FIXEDIDFOREQUALITYTEST12345",
        uko_node="project://eq-test",
        content="equal",
        token_count=5,
        provenance=_DEFAULT_PROVENANCE,
        created_at=fixed_time,
    )
    frag_b = ContextFragment(
        fragment_id="FIXEDIDFOREQUALITYTEST12345",
        uko_node="project://eq-test",
        content="equal",
        token_count=5,
        provenance=_DEFAULT_PROVENANCE,
        created_at=fixed_time,
    )
    context.frag_a = frag_a
    context.frag_b = frag_b


# ---------------------------------------------------------------------------
# Given — ContextBudget
# ---------------------------------------------------------------------------


@given("a core ContextBudget with max_tokens {max_tok:d} and reserved_tokens {res:d}")
def step_core_budget(context: Context, max_tok: int, res: int) -> None:
    context.unified_budget = ContextBudget(max_tokens=max_tok, reserved_tokens=res)


# ---------------------------------------------------------------------------
# Given — ContextPayload
# ---------------------------------------------------------------------------


@given('a core ContextPayload with plan_id "{plan_id}"')
def step_core_payload(context: Context, plan_id: str) -> None:
    context.unified_payload = ContextPayload(plan_id=plan_id)


# ---------------------------------------------------------------------------
# When — Mutation attempts
# ---------------------------------------------------------------------------


@when("I attempt to mutate the fragment uko_node")
def step_mutate_fragment(context: Context) -> None:
    context.mutation_error = None
    try:
        context.unified_fragment.uko_node = "project://mutated"  # type: ignore[misc]
    except ValidationError as exc:
        context.mutation_error = exc


@when("I attempt to mutate the budget max_tokens")
def step_mutate_budget(context: Context) -> None:
    context.mutation_error = None
    try:
        context.unified_budget.max_tokens = 9999  # type: ignore[misc]
    except ValidationError as exc:
        context.mutation_error = exc


@when("I attempt to mutate the CRP provenance resource_uri")
def step_mutate_crp_provenance(context: Context) -> None:
    context.mutation_error = None
    try:
        context.crp_provenance.resource_uri = "test://mutated"  # type: ignore[misc]
    except ValidationError as exc:
        context.mutation_error = exc


@when("I create a core ContextBudget with reserved_tokens equal to max_tokens")
def step_create_budget_equal(context: Context) -> None:
    context.strict_budget_error = None
    try:
        ContextBudget(max_tokens=100, reserved_tokens=100)
    except ValidationError as exc:
        context.strict_budget_error = exc


# ---------------------------------------------------------------------------
# Then — isinstance checks
# ---------------------------------------------------------------------------


@then("the provenance should be an instance of CRP FragmentProvenance")
def step_provenance_isinstance(context: Context) -> None:
    assert isinstance(context.provenance, CRPFragmentProvenance), (
        f"Expected isinstance of CRP FragmentProvenance, got {type(context.provenance)}"
    )


@then("the fragment should be an instance of CRP ContextFragment")
def step_fragment_isinstance(context: Context) -> None:
    assert isinstance(context.unified_fragment, CRPContextFragment), (
        f"Expected isinstance of CRP ContextFragment, "
        f"got {type(context.unified_fragment)}"
    )


@then("the budget should be an instance of CRP ContextBudget")
def step_budget_isinstance(context: Context) -> None:
    assert isinstance(context.unified_budget, CRPContextBudget), (
        f"Expected isinstance of CRP ContextBudget, got {type(context.unified_budget)}"
    )


@then("the payload should be an instance of CRP AssembledContext")
def step_payload_isinstance(context: Context) -> None:
    assert isinstance(context.unified_payload, CRPAssembledContext), (
        f"Expected isinstance of CRP AssembledContext, "
        f"got {type(context.unified_payload)}"
    )


# ---------------------------------------------------------------------------
# Then — Field value assertions
# ---------------------------------------------------------------------------


@then('the provenance resource_type should be "{value}"')
def step_provenance_resource_type(context: Context, value: str) -> None:
    assert context.provenance.resource_type == value


@then('the provenance strategy should be "{value}"')
def step_provenance_strategy(context: Context, value: str) -> None:
    assert context.provenance.strategy == value


@then('the unified fragment tier should be "{tier}"')
def step_fragment_tier(context: Context, tier: str) -> None:
    assert context.unified_fragment.tier == tier


@then("the unified fragment fragment_id should be non-empty")
def step_fragment_id_nonempty(context: Context) -> None:
    assert context.unified_fragment.fragment_id
    assert len(context.unified_fragment.fragment_id) > 0


@then("the unified fragment fragment_id should be {length:d} characters")
def step_fragment_id_length(context: Context, length: int) -> None:
    fid = context.unified_fragment.fragment_id
    assert len(fid) == length, (
        f"Expected fragment_id of {length} chars, got {len(fid)}: {fid!r}"
    )


@then("the unified fragment created_at should be a recent datetime")
def step_fragment_created_at_recent(context: Context) -> None:
    created = context.unified_fragment.created_at
    assert isinstance(created, datetime)
    now = datetime.now(UTC)
    assert now - created < timedelta(seconds=60)


@then("the unified budget available_tokens should be {tokens:d}")
def step_budget_available(context: Context, tokens: int) -> None:
    assert context.unified_budget.available_tokens == tokens


# ---------------------------------------------------------------------------
# Then — Mutation error assertions
# ---------------------------------------------------------------------------


@then("a frozen mutation error should be raised")
def step_frozen_mutation_error(context: Context) -> None:
    assert context.mutation_error is not None, (
        "Expected a frozen mutation error but none was raised"
    )


# ---------------------------------------------------------------------------
# Then — Equality assertions
# ---------------------------------------------------------------------------


@then("the two fragments should be equal")
def step_fragments_equal(context: Context) -> None:
    assert context.frag_a == context.frag_b


@then("a validation error should be raised for strict budget")
def step_strict_budget_error(context: Context) -> None:
    assert context.strict_budget_error is not None, (
        "Expected a validation error for reserved == max but none was raised"
    )
