"""Background-review usage attribution (issue #87250).

Background-review forks run with ``_session_db = None`` (persistence
isolation), so their provider-billed API calls were never recorded in
``session_model_usage``. ``_record_review_usage_to_parent`` closes that gap
by snapshotting the fork's in-memory counters and recording them against the
parent session via the aux-accounting chokepoint.
"""

from __future__ import annotations

import logging
from unittest.mock import patch

import pytest

from agent import background_review
from hermes_state import SessionDB

@pytest.fixture
def db(tmp_path):
    return SessionDB(tmp_path / "state.db")

def _usage_rows(db, session_id):
    with db._lock:
        rows = db._conn.execute(
            "SELECT * FROM session_model_usage WHERE session_id = ? ORDER BY task",
            (session_id,),
        ).fetchall()
    return [dict(r) for r in rows]

class _FakeParent:
    def __init__(self, session_db, session_id="sess-parent"):
        self._session_db = session_db
        self.session_id = session_id

def _usage(**overrides):
    base = {
        "model": "test-model",
        "provider": "test-provider",
        "base_url": "https://example.invalid/v1",
        "input_tokens": 12000,
        "output_tokens": 2400,
        "cache_read_tokens": 190000,
        "cache_write_tokens": 0,
        "reasoning_tokens": 0,
        "api_calls": 5,
        "estimated_cost_usd": 0.05,
    }
    base.update(overrides)
    return base

def test_records_fork_usage_against_parent_session(db):
    db.create_session("sess-parent", source="cli")

    background_review._record_review_usage_to_parent(_FakeParent(db), _usage())

    rows = _usage_rows(db, "sess-parent")
    assert len(rows) == 1
    r = rows[0]
    assert r["task"] == "background_review"
    assert r["model"] == "test-model"
    assert r["billing_provider"] == "test-provider"
    assert r["input_tokens"] == 12000
    assert r["output_tokens"] == 2400
    assert r["cache_read_tokens"] == 190000
    assert r["api_call_count"] == 5
    assert r.get("estimated_cost_usd") == 0.05

def test_accumulates_repeated_forks_same_model(db):
    db.create_session("sess-parent", source="cli")
    parent = _FakeParent(db)

    background_review._record_review_usage_to_parent(parent, _usage(api_calls=5))
    background_review._record_review_usage_to_parent(parent, _usage(api_calls=7))

    rows = _usage_rows(db, "sess-parent")
    assert len(rows) == 1
    assert rows[0]["input_tokens"] == 24000
    assert rows[0]["api_call_count"] == 12

def test_noop_when_fork_made_no_calls(db):
    db.create_session("sess-parent", source="cli")

    background_review._record_review_usage_to_parent(
        _FakeParent(db),
        _usage(
            input_tokens=0,
            output_tokens=0,
            cache_read_tokens=0,
            cache_write_tokens=0,
            reasoning_tokens=0,
            api_calls=0,
        ),
    )

    assert _usage_rows(db, "sess-parent") == []

def test_noop_when_parent_has_no_session_id(db):
    db.create_session("sess-parent", source="cli")

    background_review._record_review_usage_to_parent(
        _FakeParent(db, session_id=""), _usage()
    )

    assert _usage_rows(db, "sess-parent") == []

def test_survives_accounting_failure():
    class _BoomDB:
        def record_auxiliary_usage(self, *args, **kwargs):
            raise RuntimeError("simulated accounting failure")

    background_review._record_review_usage_to_parent(_FakeParent(_BoomDB()), _usage())

def test_classify_review_result():
    assert background_review._classify_review_result([]) == "none"
    assert background_review._classify_review_result(["Memory updated"]) == "memory"
    assert background_review._classify_review_result(["Skill 'x' patched"]) == "skill"
    assert (
        background_review._classify_review_result(
            ["Memory updated", "Skill 'x' created"]
        )
        == "skill+memory"
    )
    # Prefix-based — free-text "skill"/"memory" elsewhere must not misclassify.
    assert (
        background_review._classify_review_result(
            ["Skipped: no skill worth saving"]
        )
        == "none"
    )
    assert (
        background_review._classify_review_result(
            ["📝 Skill 'deploy' patched: \"a\" → \"b\""]
        )
        == "skill"
    )
    assert (
        background_review._classify_review_result(["User profile ➕ prefers terse"])
        == "memory"
    )

def test_enabled_config_failure_logs_warning(caplog):
    with patch(
        "hermes_cli.config.load_config_readonly",
        side_effect=RuntimeError("boom"),
    ), caplog.at_level(logging.WARNING, logger="agent.background_review"):
        assert background_review.load_background_review_settings()[0] is True
    assert any(r.levelno >= logging.WARNING for r in caplog.records)
