# tests/test_agent_base.py
"""Tests for AgentBase ABC."""
import json
import pytest
from unittest.mock import AsyncMock, patch

from nthlayer_respond.agents.base import AgentBase
from nthlayer_respond.types import (
    AgentRole, IncidentContext, IncidentState, TriageResult,
)


class StubAgent(AgentBase):
    """Minimal concrete agent for testing the base class."""
    role = AgentRole.TRIAGE
    default_timeout = 5

    def build_prompt(self, context):
        return ("You are a test agent.", "Assess this incident.")

    def parse_response(self, response, context):
        data = self._parse_json(response)
        return TriageResult(
            severity=data.get("severity", 2),
            blast_radius=data.get("blast_radius", []),
            affected_slos=[],
            assigned_team=None,
            reasoning=data.get("reasoning", ""),
        )

    def _apply_result(self, context, result):
        context.triage = result
        return context


@pytest.fixture
def stub_agent(verdict_store):
    return StubAgent(
        model="test-model",
        max_tokens=100,
        verdict_store=verdict_store,
        config={},
    )


@pytest.fixture
def triggered_context():
    return IncidentContext(
        id="INC-2026-0001",
        state=IncidentState.TRIGGERED,
        created_at="2026-03-19T10:00:00Z",
        updated_at="2026-03-19T10:00:00Z",
        trigger_source="sitrep",
        trigger_verdict_ids=["vrd-trigger-001"],
        topology={},
    )


def test_emit_verdict_creates_verdict(stub_agent, verdict_store, triggered_context):
    v = stub_agent._emit_verdict(
        triggered_context,
        subject_summary="Test triage",
        action="flag",
        confidence=0.8,
        reasoning="test reasoning",
    )
    assert v.subject.type == "triage"
    assert v.producer.system == "nthlayer-respond"
    assert v.judgment.action == "flag"
    assert v.judgment.confidence == 0.8
    assert v.lineage.context == ["vrd-trigger-001"]
    assert v.lineage.parent is None  # first in chain
    assert v.id in triggered_context.verdict_chain
    # Verify persisted
    assert verdict_store.get(v.id) is not None


def test_emit_verdict_chains_parent(stub_agent, verdict_store, triggered_context):
    v1 = stub_agent._emit_verdict(
        triggered_context, "first", "flag", 0.8, "first verdict",
    )
    v2 = stub_agent._emit_verdict(
        triggered_context, "second", "flag", 0.7, "second verdict",
    )
    assert v2.lineage.parent == v1.id
    assert len(triggered_context.verdict_chain) == 2


def test_degraded_verdict(stub_agent, verdict_store, triggered_context):
    v = stub_agent._degraded_verdict(triggered_context, "model timeout")
    assert v.judgment.action == "escalate"
    assert v.judgment.confidence == 0.0
    assert "degraded" in v.judgment.tags
    assert "human-takeover-required" in v.judgment.tags
    assert v.id in triggered_context.verdict_chain


def test_parse_json_clean(stub_agent):
    result = stub_agent._parse_json('{"key": "value"}')
    assert result == {"key": "value"}


def test_parse_json_markdown_fences(stub_agent):
    result = stub_agent._parse_json('```json\n{"key": "value"}\n```')
    assert result == {"key": "value"}


def test_parse_json_preamble(stub_agent):
    result = stub_agent._parse_json('Here is the JSON:\n{"key": "value"}')
    assert result == {"key": "value"}


def test_parse_json_invalid(stub_agent):
    with pytest.raises(ValueError):
        stub_agent._parse_json("not json at all")


async def test_execute_success(stub_agent, triggered_context, verdict_store):
    model_response = json.dumps({
        "severity": 1,
        "blast_radius": ["payment-api"],
        "reasoning": "Critical service affected",
    })
    with patch.object(stub_agent, "_call_model", new_callable=AsyncMock, return_value=model_response):
        result = await stub_agent.execute(triggered_context)
    assert result.triage is not None
    assert result.triage.severity == 1
    assert len(result.verdict_chain) == 1


async def test_execute_model_failure_degrades(stub_agent, triggered_context, verdict_store):
    with patch.object(stub_agent, "_call_model", new_callable=AsyncMock, side_effect=Exception("API down")):
        result = await stub_agent.execute(triggered_context)
    assert result.triage is None  # no result applied
    assert len(result.verdict_chain) == 1  # degraded verdict emitted
    v = verdict_store.get(result.verdict_chain[0])
    assert v.judgment.action == "escalate"
    assert v.judgment.confidence == 0.0
