"""Tests for MoA aggregator-slot exposure used by session cost accounting.

Regression guard for the ~50% MoA cost undercount: on the MoA path the
agent's model/provider are the virtual preset name (e.g. "closed") and "moa",
which have no pricing entry. Session cost accounting must price the
aggregator's acting turn at its REAL model/provider, read from the MoA
client's ``last_aggregator_slot``. Before the fix that slot did not exist and
the aggregator's spend (often >50% of the turn) was silently dropped, leaving
the session cost as advisor-fan-out only.
"""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import patch

import pytest


def _response(content="ok"):
    message = SimpleNamespace(content=content, tool_calls=[])
    choice = SimpleNamespace(message=message, finish_reason="stop")
    return SimpleNamespace(choices=[choice], usage=None, model="fake")


@pytest.fixture
def moa_config(tmp_path, monkeypatch):
    home = tmp_path / ".hermes"
    home.mkdir()
    (home / "config.yaml").write_text(
        """
moa:
  default_preset: closed
  presets:
    closed:
      enabled: true
      reference_models:
        - provider: openrouter
          model: anthropic/claude-opus-4.8
        - provider: openrouter
          model: openai/gpt-5.5
      aggregator:
        provider: openrouter
        model: anthropic/claude-opus-4.8
""".strip(),
        encoding="utf-8",
    )
    monkeypatch.setenv("HERMES_HOME", str(home))
    return home


def test_create_populates_last_aggregator_slot(moa_config, monkeypatch):
    """After a create() turn, last_aggregator_slot carries the REAL aggregator
    model/provider — not the virtual preset name."""
    from agent.moa_loop import MoAChatCompletions

    def fake_call_llm(**kwargs):
        return _response("acted" if kwargs.get("task") != "moa_reference" else "advice")

    monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)

    facade = MoAChatCompletions("closed")
    # Slot is unset before any turn runs.
    assert facade.last_aggregator_slot is None

    facade.create(
        model="closed",
        messages=[{"role": "user", "content": "clean the db"}],
    )

    slot = facade.last_aggregator_slot
    assert slot is not None
    # The virtual preset name / "moa" must NOT leak into the priced slot.
    assert slot["model"] == "anthropic/claude-opus-4.8"
    assert slot["provider"] == "openrouter"
    assert slot["model"] != "closed"


def test_client_exposes_last_aggregator_slot(moa_config, monkeypatch):
    """MoAClient delegates last_aggregator_slot to its completions facade so
    session accounting can read it without touching internals."""
    from agent.moa_loop import MoAClient

    def fake_call_llm(**kwargs):
        return _response("acted" if kwargs.get("task") != "moa_reference" else "advice")

    monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)

    client = MoAClient("closed")
    assert client.last_aggregator_slot is None

    client.chat.completions.create(
        model="closed",
        messages=[{"role": "user", "content": "clean the db"}],
    )

    slot = client.last_aggregator_slot
    assert slot is not None
    assert slot["model"] == "anthropic/claude-opus-4.8"
    assert slot["provider"] == "openrouter"
