"""Helper script for tdd_context_tier_runtime.robot smoke tests.

Each subcommand exercises ``ContextTierService`` to reproduce bug #821.
The helper reports the **real** outcome: it exits 0 and prints the sentinel
when the expected behaviour is observed (bug fixed), and exits 1 when the
bug is still present.  The ``tdd_expected_fail_listener`` on the Robot side
handles pass/fail inversion while the bug remains open.
"""

from __future__ import annotations

import sys
from collections.abc import Callable
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import NoReturn

# Ensure local source tree is importable.
_ROOT: Path = Path(__file__).resolve().parents[1]
_SRC: str = str(_ROOT / "src")
_ROBOT: str = str(_ROOT / "robot")
for _p in (_SRC, _ROBOT):
    if _p not in sys.path:
        sys.path.insert(0, _p)

from cleveragents.application.services.context_tiers import (  # noqa: E402
    ContextTierService,
)
from cleveragents.domain.models.acms.tiers import (  # noqa: E402
    ContextTier,
    TierBudget,
    TieredFragment,
)


def _fail(msg: str) -> NoReturn:
    """Print error message to stderr and exit with code 1."""
    print(msg, file=sys.stderr)
    sys.exit(1)


def _make_fragment(
    fragment_id: str,
    tier: ContextTier,
    token_count: int = 50,
    content: str = "test content",
    last_accessed: datetime | None = None,
) -> TieredFragment:
    """Create a ``TieredFragment`` with the given properties."""
    kwargs: dict[str, object] = {
        "fragment_id": fragment_id,
        "content": content,
        "tier": tier,
        "token_count": token_count,
        "project_name": "test-project",
    }
    if last_accessed is not None:
        kwargs["last_accessed"] = last_accessed
    return TieredFragment(**kwargs)  # type: ignore[arg-type]


# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------


def _promote_on_access() -> None:
    """Verify that accessing a cold-tier fragment repeatedly auto-promotes it.

    Bug #821: ``get()`` increments ``access_count`` and updates
    ``last_accessed`` but never calls ``promote()`` or any equivalent
    runtime logic.  The fragment stays in the cold tier forever.
    """
    service: ContextTierService = ContextTierService(settings=None)
    frag: TieredFragment = _make_fragment(
        fragment_id="frag-promote-001",
        tier=ContextTier.COLD,
        token_count=50,
    )
    service.store(frag)

    # Access the fragment 5 times
    for _ in range(5):
        result: TieredFragment | None = service.get("frag-promote-001")
        if result is None:
            _fail("Fragment disappeared during repeated access")

    # Check if the fragment was promoted
    final: TieredFragment | None = service.get("frag-promote-001")
    if final is None:
        _fail("Fragment not found after repeated access")

    if final.tier == ContextTier.COLD:
        _fail(
            f"Fragment is still in COLD tier after 5 accesses "
            f"(access_count={final.access_count}). "
            f"ContextTierService.get() does not auto-promote fragments "
            f"based on access patterns (bug #821)."
        )
    print("tdd-promote-on-access-ok")


def _demote_on_staleness() -> None:
    """Verify that a stale hot-tier fragment is auto-demoted.

    Bug #821: ContextTierService has no staleness enforcement method.
    No ``enforce_staleness()``, ``apply_tier_policy()``, ``tick()``,
    or similar runtime method exists.
    """
    service: ContextTierService = ContextTierService(settings=None)
    stale_time: datetime = datetime.now(tz=UTC) - timedelta(hours=24)
    frag: TieredFragment = _make_fragment(
        fragment_id="frag-stale-001",
        tier=ContextTier.HOT,
        token_count=100,
        last_accessed=stale_time,
    )
    service.store(frag)

    # Attempt to invoke staleness enforcement
    invoked: bool = False
    for method_name in (
        "enforce_staleness",
        "apply_tier_policy",
        "tick",
        "enforce_demotion_policy",
        "run_lifecycle",
        "apply_lifecycle",
    ):
        method: object = getattr(service, method_name, None)
        if callable(method):
            method()
            invoked = True
            break

    if not invoked:
        _fail(
            "No staleness enforcement method found on ContextTierService. "
            "Tried: enforce_staleness(), apply_tier_policy(), tick(), "
            "enforce_demotion_policy(), run_lifecycle(), apply_lifecycle(). "
            "ContextTierService has no runtime logic for automatic "
            "demotion of stale fragments (bug #821)."
        )

    # Check if the fragment was demoted
    result: TieredFragment | None = service.get("frag-stale-001")
    if result is None:
        _fail("Stale fragment not found after staleness enforcement")

    if result.tier == ContextTier.HOT:
        _fail(
            f"Stale fragment is still in HOT tier "
            f"(last_accessed={result.last_accessed}). "
            f"Staleness enforcement did not demote it (bug #821)."
        )
    print("tdd-demote-on-staleness-ok")


def _evict_on_overflow() -> None:
    """Verify that hot tier budget overflow triggers auto-eviction.

    Bug #821: ``store()`` does not check ``TierBudget.max_tokens_hot``
    and does not call ``evict_lru()`` when the budget is exceeded.
    """
    service: ContextTierService = ContextTierService(settings=None)
    service._budget = TierBudget(
        max_tokens_hot=100,
        max_decisions_warm=500,
        max_decisions_cold=5000,
    )

    # Fill hot tier to exactly 100 tokens (2 x 50-token fragments)
    for i in range(2):
        frag: TieredFragment = _make_fragment(
            fragment_id=f"frag-budget-{i:03d}",
            tier=ContextTier.HOT,
            token_count=50,
            content="x" * 50,
            last_accessed=datetime.now(tz=UTC) - timedelta(minutes=2 - i),
        )
        service.store(frag)

    oldest_id: str = "frag-budget-000"

    # Store one more fragment that exceeds the budget
    overflow: TieredFragment = _make_fragment(
        fragment_id="frag-overflow-001",
        tier=ContextTier.HOT,
        token_count=50,
        content="overflow",
    )
    service.store(overflow)

    # Check hot tier total tokens
    hot_total: int = sum(f.token_count for f in service._hot.values())
    if hot_total > 100:
        _fail(
            f"Hot tier has {hot_total} tokens but budget is 100 tokens. "
            f"store() does not enforce TierBudget.max_tokens_hot or "
            f"trigger automatic LRU eviction (bug #821)."
        )

    # Check that the oldest fragment was evicted
    oldest: TieredFragment | None = service.get(oldest_id)
    if oldest is not None and oldest.tier == ContextTier.HOT:
        _fail(
            f"Oldest fragment '{oldest_id}' is still in hot tier after "
            f"budget overflow. store() does not auto-evict (bug #821)."
        )

    print("tdd-evict-on-overflow-ok")


# ---------------------------------------------------------------------------
# Dispatcher
# ---------------------------------------------------------------------------

_COMMANDS: dict[str, Callable[[], None]] = {
    "promote-on-access": _promote_on_access,
    "demote-on-staleness": _demote_on_staleness,
    "evict-on-overflow": _evict_on_overflow,
}

if __name__ == "__main__":
    if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
        print(
            f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>",
            file=sys.stderr,
        )
        sys.exit(1)
    cmd: Callable[[], None] = _COMMANDS[sys.argv[1]]
    cmd()
