"""Step definitions for ContextTierService scope enforcement tests.

Extracted from ``scoped_view_steps.py`` to keep step files manageable.
All steps prefixed with ``sv`` to avoid AmbiguousStep collisions.
"""

from __future__ import annotations

from typing import Any

from behave import given, then, when  # type: ignore[import-untyped]

from cleveragents.domain.models.acms.scope_resolution import (
    resolve_resource_scope,
)
from cleveragents.domain.models.acms.scoped_view import (
    ScopeViolationError,
)
from cleveragents.domain.models.acms.tiers import (
    ContextTier,
    TieredFragment,
)

__all__: list[str] = []


def _make_fragment(
    fid: str,
    project: str = "",
    resource: str = "",
    tier: str = "hot",
) -> TieredFragment:
    """Create a TieredFragment for testing."""
    return TieredFragment(
        fragment_id=fid,
        content=f"content-{fid}",
        tier=ContextTier(tier),
        project_name=project,
        resource_id=resource,
    )


# ---------------------------------------------------------------------------
# ContextTierService: scope enforcement
# ---------------------------------------------------------------------------


@given("sv a ContextTierService")
def step_sv_tier_service(context: Any) -> None:
    from cleveragents.application.services.context_tiers import ContextTierService

    context.sv_tier_svc = ContextTierService()


@when('sv I store fragment "{fid}" project "{proj}" resource "{res}" tier "{tier}"')
def step_sv_store_fragment(
    context: Any, fid: str, proj: str, res: str, tier: str
) -> None:
    frag = _make_fragment(fid, project=proj, resource=res, tier=tier)
    context.sv_tier_svc.store(frag)


@then("sv get_scoped_by_resource should return {count:d} fragment")
def step_sv_scoped_by_resource(context: Any, count: int) -> None:
    result = context.sv_tier_svc.get_scoped_by_resource(context.sv_scope)
    assert len(result) == count, f"Expected {count} fragments, got {len(result)}"


@then('sv storing fragment with resource "{res}" should raise ScopeViolationError')
def step_sv_store_scope_check(context: Any, res: str) -> None:
    frag = _make_fragment("bad-frag", project="proj-a", resource=res)
    try:
        context.sv_tier_svc.store_with_scope_check(frag, context.sv_scope)
        raise AssertionError("Expected ScopeViolationError")
    except ScopeViolationError:
        pass


@then(
    'sv storing fragment with project "{proj}" and resource "{res}"'
    " should raise ScopeViolationError"
)
def step_sv_store_scope_check_project(context: Any, proj: str, res: str) -> None:
    frag = _make_fragment("bad-proj-frag", project=proj, resource=res)
    try:
        context.sv_tier_svc.store_with_scope_check(frag, context.sv_scope)
        raise AssertionError("Expected ScopeViolationError for out-of-scope project")
    except ScopeViolationError:
        pass


@then('sv validating fragment "{fid}" against the scope should pass')
def step_sv_validate_fragment(context: Any, fid: str) -> None:
    context.sv_tier_svc.validate_fragment_scope(fid, context.sv_scope)


@then(
    'sv validating fragment "{fid}" against the scope should raise ScopeViolationError'
)
def step_sv_validate_fragment_fail(context: Any, fid: str) -> None:
    try:
        context.sv_tier_svc.validate_fragment_scope(fid, context.sv_scope)
        raise AssertionError("Expected ScopeViolationError")
    except ScopeViolationError:
        pass


# ---------------------------------------------------------------------------
# Coverage: context_tiers store empty id
# ---------------------------------------------------------------------------


@then("sv storing fragment with empty id should raise ValueError")
def step_sv_store_empty_id(context: Any) -> None:
    frag = TieredFragment.model_construct(
        fragment_id="",
        content="content",
        tier=ContextTier.HOT,
        project_name="p",
        resource_id="R1",
    )
    try:
        context.sv_tier_svc.store(frag)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


# ---------------------------------------------------------------------------
# Coverage: context_tiers get_scoped_view empty
# ---------------------------------------------------------------------------


@then("sv get_scoped_view with empty projects should return empty list")
def step_sv_scoped_view_empty(context: Any) -> None:
    result = context.sv_tier_svc.get_scoped_view([])
    assert result == []


# ---------------------------------------------------------------------------
# Coverage: context_tiers evict_lru empty
# ---------------------------------------------------------------------------


@then("sv evict_lru on empty cold tier should return empty list")
def step_sv_evict_empty(context: Any) -> None:
    result = context.sv_tier_svc.evict_lru(ContextTier.COLD, 1)
    assert result == []


# ---------------------------------------------------------------------------
# Coverage: context_tiers get_scoped_metrics
# ---------------------------------------------------------------------------


@then('sv get_scoped_metrics for "{proj}" should show {h:d} hot and {w:d} warm')
def step_sv_scoped_metrics(context: Any, proj: str, h: int, w: int) -> None:
    metrics = context.sv_tier_svc.get_scoped_metrics([proj])
    assert metrics.hot_count == h, f"Expected hot={h}, got {metrics.hot_count}"
    assert metrics.warm_count == w, f"Expected warm={w}, got {metrics.warm_count}"


# ---------------------------------------------------------------------------
# Coverage: context_tiers validate_fragment_scope not found
# ---------------------------------------------------------------------------


@then("sv validating non-existent fragment should raise ValueError")
def step_sv_validate_nonexistent(context: Any) -> None:
    try:
        context.sv_tier_svc.validate_fragment_scope("ghost", context.sv_scope)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


# ---------------------------------------------------------------------------
# Coverage: context_tiers store_with_scope_check happy path
# ---------------------------------------------------------------------------


@then("sv store_with_scope_check should accept in-scope fragment")
def step_sv_store_scope_ok(context: Any) -> None:
    frag = _make_fragment("ok-frag", project="proj-a", resource="RES01")
    context.sv_tier_svc.store_with_scope_check(frag, context.sv_scope)
    result = context.sv_tier_svc.get("ok-frag")
    assert result is not None


# ---------------------------------------------------------------------------
# Coverage: context_tiers budget property
# ---------------------------------------------------------------------------


@then("sv tier service budget should be a TierBudget")
def step_sv_budget(context: Any) -> None:
    from cleveragents.domain.models.acms.tiers import TierBudget

    budget = context.sv_tier_svc.budget
    assert isinstance(budget, TierBudget)


# ---------------------------------------------------------------------------
# Coverage: context_tiers get_for_actor no project filter
# ---------------------------------------------------------------------------


@then("sv get_for_actor without projects should return a list")
def step_sv_actor_no_projects(context: Any) -> None:
    from cleveragents.domain.models.acms.tiers import ActorRole

    result = context.sv_tier_svc.get_for_actor(ActorRole.STRATEGIST)
    assert isinstance(result, list)


# ---------------------------------------------------------------------------
# Coverage: context_tiers _summarize_for_cold false branch
# ---------------------------------------------------------------------------


@when('sv I store a short warm fragment "{fid}"')
def step_sv_store_short_warm(context: Any, fid: str) -> None:
    frag = TieredFragment(
        fragment_id=fid,
        content="short",
        tier=ContextTier.WARM,
        project_name="proj-a",
        resource_id="RES01",
    )
    context.sv_tier_svc.store(frag)


@then('sv demoting "{fid}" to cold should keep content intact')
def step_sv_demote_short(context: Any, fid: str) -> None:
    result = context.sv_tier_svc.demote(fid)
    assert result is not None
    assert result.content == "short"
    assert result.tier == ContextTier.COLD


# ---------------------------------------------------------------------------
# P0-S2: get_scoped
# ---------------------------------------------------------------------------


@then('sv get_scoped for "{fid}" should return the fragment')
def step_sv_get_scoped_found(context: Any, fid: str) -> None:
    result = context.sv_tier_svc.get_scoped(fid, context.sv_scope)
    assert result is not None
    assert result.fragment_id == fid


@then('sv get_scoped for "{fid}" should return None')
def step_sv_get_scoped_none(context: Any, fid: str) -> None:
    result = context.sv_tier_svc.get_scoped(fid, context.sv_scope)
    assert result is None


# ---------------------------------------------------------------------------
# P1-SPEC1: DAG expansion
# ---------------------------------------------------------------------------


@when('sv I resolve scope with registry_lookup expanding "{parent}" to "{child}"')
def step_sv_resolve_with_dag(context: Any, parent: str, child: str) -> None:
    def lookup(rid: str) -> frozenset[str]:
        if rid == parent:
            return frozenset([child])
        return frozenset()

    context.sv_scope = resolve_resource_scope(
        [context.sv_project],
        registry_lookup=lookup,
    )


@when(
    'sv I store fragment "{fid}" with project "{proj}" and empty resource_id in tier "{tier}"'
)
def step_sv_store_empty_rid(context: Any, fid: str, proj: str, tier: str) -> None:
    frag = _make_fragment(fid, project=proj, resource="", tier=tier)
    context.sv_tier_svc.store(frag)
