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

Each subcommand exercises ``ContextTierService`` to reproduce bug #1152.
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.

Bug #1152: ``_enforce_hot_budget()`` permanently deletes hot-tier fragments
via ``del self._hot[oldest_id]`` instead of demoting them to the warm tier.
Similarly, ``evict_lru()`` permanently deletes via ``del store[fid]``.
"""

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."""
    fragment: TieredFragment = TieredFragment(
        fragment_id=fragment_id,
        content=content,
        tier=tier,
        token_count=token_count,
        project_name="test-project",
    )
    if last_accessed is not None:
        fragment.last_accessed = last_accessed
    return fragment


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


def _budget_eviction_demotes() -> None:
    """Verify that budget-evicted hot-tier fragment is demoted to warm.

    Bug #1152: ``_enforce_hot_budget()`` does
    ``del self._hot[oldest_id]`` which permanently destroys the
    fragment.  The correct behaviour is to demote to the warm tier.
    """
    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)
    frag_a: TieredFragment = _make_fragment(
        fragment_id="frag-evict-oldest",
        tier=ContextTier.HOT,
        token_count=50,
        content="oldest fragment",
        last_accessed=datetime.now(tz=UTC) - timedelta(minutes=10),
    )
    service.store(frag_a)

    frag_b: TieredFragment = _make_fragment(
        fragment_id="frag-evict-newer",
        tier=ContextTier.HOT,
        token_count=50,
        content="newer fragment",
        last_accessed=datetime.now(tz=UTC) - timedelta(minutes=5),
    )
    service.store(frag_b)

    # Store one more fragment to trigger budget eviction
    overflow: TieredFragment = _make_fragment(
        fragment_id="frag-evict-overflow",
        tier=ContextTier.HOT,
        token_count=50,
        content="overflow fragment",
    )
    service.store(overflow)

    # Check: the oldest fragment should NOT be in hot tier
    if "frag-evict-oldest" in service._hot:
        _fail(
            "Fragment 'frag-evict-oldest' is still in hot tier after "
            "budget overflow — budget enforcement did not evict it."
        )

    # Check: the oldest fragment SHOULD be in warm tier (demoted, not deleted)
    if "frag-evict-oldest" not in service._warm:
        _fail(
            "Fragment 'frag-evict-oldest' was evicted from hot tier but "
            "is NOT in the warm tier. _enforce_hot_budget() permanently "
            "deletes evicted fragments instead of demoting them to warm "
            "(bug #1152)."
        )

    # Verify tier metadata
    demoted: TieredFragment = service._warm["frag-evict-oldest"]
    if demoted.tier != ContextTier.WARM:
        _fail(
            f"Fragment is in warm store but tier metadata is "
            f"{demoted.tier}, expected WARM."
        )

    print("tdd-budget-eviction-demotes-ok")


def _evict_lru_demotes() -> None:
    """Verify that evict_lru() demotes to warm instead of deleting.

    Bug #1152: ``evict_lru()`` does ``del store[fid]`` which
    permanently destroys the fragment.  The correct behaviour is to
    demote to the next lower tier.
    """
    service: ContextTierService = ContextTierService(settings=None)
    service._budget = TierBudget(
        max_tokens_hot=100,
        max_decisions_warm=500,
        max_decisions_cold=5000,
    )

    # Store two fragments in hot tier
    frag_a: TieredFragment = _make_fragment(
        fragment_id="frag-lru-oldest",
        tier=ContextTier.HOT,
        token_count=50,
        content="oldest fragment",
        last_accessed=datetime.now(tz=UTC) - timedelta(minutes=10),
    )
    service.store(frag_a)

    frag_b: TieredFragment = _make_fragment(
        fragment_id="frag-lru-newer",
        tier=ContextTier.HOT,
        token_count=50,
        content="newer fragment",
        last_accessed=datetime.now(tz=UTC) - timedelta(minutes=5),
    )
    service.store(frag_b)

    # Evict the LRU fragment
    evicted_ids: list[str] = service.evict_lru(ContextTier.HOT, count=1)

    if len(evicted_ids) != 1:
        _fail(f"Expected 1 evicted fragment, got {len(evicted_ids)}")

    evicted_id: str = evicted_ids[0]
    if evicted_id != "frag-lru-oldest":
        _fail(
            f"Expected oldest fragment to be evicted, but '{evicted_id}' was evicted."
        )

    # Check: the evicted fragment should NOT be in hot tier
    if evicted_id in service._hot:
        _fail(f"Fragment '{evicted_id}' is still in hot tier after eviction.")

    # Check: the evicted fragment SHOULD be in warm tier (demoted, not deleted)
    if evicted_id not in service._warm:
        _fail(
            f"Fragment '{evicted_id}' was evicted from hot tier but is "
            f"NOT in the warm tier. evict_lru() permanently deletes "
            f"evicted fragments instead of demoting them to warm "
            f"(bug #1152)."
        )

    # Verify tier metadata
    demoted: TieredFragment = service._warm[evicted_id]
    if demoted.tier != ContextTier.WARM:
        _fail(
            f"Fragment is in warm store but tier metadata is "
            f"{demoted.tier}, expected WARM."
        )

    print("tdd-evict-lru-demotes-ok")


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

_COMMANDS: dict[str, Callable[[], None]] = {
    "budget-eviction-demotes": _budget_eviction_demotes,
    "evict-lru-demotes": _evict_lru_demotes,
}

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()
