"""
Tests for agent, model, and flow Pydantic models.

Validates serialization, deserialization, defaults, and computed properties.
"""

import pytest

from boring_os.agent_models import (
    AgentDefinition,
    AgentListResponse,
    AgentResponse,
    AgentSessionInfo,
    AgentStreamChunk,
    AgentSummary,
    TokenUsage,
)
from boring_os.model_models import (
    ModelCompletion,
    ModelEmbedding,
    ModelInfo,
    ModelStreamChunk,
    ModelUsage,
)
from boring_os.flow_models import (
    FlowDefinition,
    FlowEdge,
    FlowListResponse,
    FlowNode,
    FlowNodeExecution,
    FlowResult,
    FlowStreamChunk,
)


# =============================================================================
# Agent Model Tests
# =============================================================================

class TestAgentResponse:
    """Tests for AgentResponse model."""

    def test_parse_full_response(self):
        """Full response data parses correctly."""
        data = {
            "text": "Hello from the agent",
            "tool_calls": [{"tool": "foo", "result": {}}],
            "token_usage": {"input": 100, "output": 50, "cost": 0.005},
            "iterations": 3,
            "model_used": "claude-sonnet-4",
            "session_id": "ses-123",
            "request_id": "req-456",
        }
        resp = AgentResponse.model_validate(data)
        assert resp.text == "Hello from the agent"
        assert len(resp.tool_calls) == 1
        assert resp.iterations == 3
        assert resp.cost == 0.005

    def test_defaults(self):
        """Missing optional fields use defaults."""
        resp = AgentResponse(text="hi", request_id="r1")
        assert resp.tool_calls == []
        assert resp.iterations == 1
        assert resp.session_id is None
        assert resp.cost == 0.0

    def test_cost_property(self):
        """Cost property delegates to token_usage."""
        resp = AgentResponse(
            text="x",
            request_id="r1",
            token_usage=TokenUsage(input=10, output=5, cost=0.123),
        )
        assert resp.cost == 0.123


class TestAgentStreamChunk:
    """Tests for AgentStreamChunk model."""

    def test_intermediate_chunk(self):
        """Non-final chunk has text but no usage."""
        chunk = AgentStreamChunk(text="Hello ")
        assert not chunk.done
        assert chunk.token_usage is None
        assert not chunk.node_changed

    def test_final_chunk(self):
        """Final chunk has done=True and token_usage."""
        chunk = AgentStreamChunk(
            text="",
            done=True,
            token_usage=TokenUsage(input=100, output=50, cost=0.01),
        )
        assert chunk.done
        assert chunk.token_usage is not None
        assert chunk.token_usage.cost == 0.01


class TestAgentDefinition:
    """Tests for AgentDefinition model."""

    def test_parse_definition(self):
        """Agent definition parses with all fields."""
        data = {
            "name": "billing_agent",
            "model": "claude-sonnet-4",
            "tools": ["get_billing"],
            "max_iterations": 25,
            "status": "active",
            "version": "2",
        }
        defn = AgentDefinition.model_validate(data)
        assert defn.name == "billing_agent"
        assert defn.tools == ["get_billing"]
        assert defn.max_iterations == 25

    def test_defaults(self):
        """Missing optional fields use defaults."""
        defn = AgentDefinition(name="test")
        assert defn.tools == []
        assert defn.max_iterations == 25
        assert defn.max_total_tokens == 100_000
        assert defn.status == "active"


class TestAgentListResponse:
    """Tests for AgentListResponse model."""

    def test_parse_list(self):
        """Agent list parses correctly."""
        data = {
            "agents": [
                {"name": "a1", "tools_count": 2},
                {"name": "a2", "tools_count": 0},
            ],
            "total": 2,
        }
        resp = AgentListResponse.model_validate(data)
        assert resp.total == 2
        assert len(resp.agents) == 2
        assert resp.agents[0].name == "a1"


class TestAgentSessionInfo:
    """Tests for AgentSessionInfo model."""

    def test_parse_session(self):
        """Session info parses correctly."""
        info = AgentSessionInfo(
            session_id="ses-1",
            agent_name="billing_agent",
            total_messages=4,
            total_cost=0.05,
        )
        assert info.session_id == "ses-1"
        assert info.total_messages == 4


# =============================================================================
# Model Model Tests
# =============================================================================

class TestModelCompletion:
    """Tests for ModelCompletion model."""

    def test_parse_completion(self):
        """Full completion parses correctly."""
        data = {
            "text": "Gravity is...",
            "model": "claude-sonnet-4",
            "provider": "anthropic",
            "usage": {"input_tokens": 25, "output_tokens": 50, "cost": 0.001},
            "finish_reason": "stop",
        }
        comp = ModelCompletion.model_validate(data)
        assert comp.text == "Gravity is..."
        assert comp.cost == 0.001

    def test_defaults(self):
        """Default values are sensible."""
        comp = ModelCompletion()
        assert comp.text == ""
        assert comp.finish_reason == "stop"
        assert comp.tool_calls == []


class TestModelEmbedding:
    """Tests for ModelEmbedding model."""

    def test_parse_embedding(self):
        """Embedding response parses correctly."""
        data = {
            "embeddings": [[0.1, 0.2], [0.3, 0.4]],
            "model": "text-embedding-3-small",
            "usage": {"total_tokens": 10},
        }
        emb = ModelEmbedding.model_validate(data)
        assert len(emb.embeddings) == 2
        assert emb.model == "text-embedding-3-small"


class TestModelInfo:
    """Tests for ModelInfo model."""

    def test_parse_model_info(self):
        """Model info parses correctly."""
        info = ModelInfo(
            name="claude-sonnet-4",
            provider="anthropic",
            input_price_per_million=3.0,
            output_price_per_million=15.0,
            max_context_tokens=200000,
        )
        assert info.name == "claude-sonnet-4"
        assert info.max_context_tokens == 200000


# =============================================================================
# Flow Model Tests
# =============================================================================

class TestFlowResult:
    """Tests for FlowResult model."""

    def test_parse_result(self):
        """Flow result parses correctly."""
        data = {
            "text": "Done",
            "nodes_executed": [
                {"node": "triage", "agent": "triage_agent", "duration_ms": 100},
            ],
            "total_cost": 0.05,
            "execution_id": "ex-1",
            "total_duration_ms": 500,
        }
        result = FlowResult.model_validate(data)
        assert result.text == "Done"
        assert result.node_count == 1
        assert result.total_cost == 0.05

    def test_node_count_property(self):
        """node_count returns length of nodes_executed."""
        result = FlowResult(
            nodes_executed=[
                FlowNodeExecution(node="a", agent="a_agent"),
                FlowNodeExecution(node="b", agent="b_agent"),
            ]
        )
        assert result.node_count == 2


class TestFlowStreamChunk:
    """Tests for FlowStreamChunk model."""

    def test_node_transition(self):
        """Node transition chunk has flags set."""
        chunk = FlowStreamChunk(node_changed=True, current_node="billing")
        assert chunk.node_changed
        assert chunk.current_node == "billing"

    def test_final_chunk(self):
        """Final chunk includes cost and nodes_executed."""
        chunk = FlowStreamChunk(done=True, total_cost=0.05)
        assert chunk.done
        assert chunk.total_cost == 0.05


class TestFlowDefinition:
    """Tests for FlowDefinition model."""

    def test_parse_definition(self):
        """Flow definition parses correctly."""
        data = {
            "name": "support_flow",
            "nodes": [
                {"name": "triage", "agent": "triage_agent"},
            ],
            "edges": [
                {
                    "source": "triage",
                    "target": "billing",
                    "condition_type": "keyword_match",
                    "condition_value": "billing",
                    "operation": "HANDOFF",
                },
            ],
        }
        defn = FlowDefinition.model_validate(data)
        assert defn.name == "support_flow"
        assert len(defn.nodes) == 1
        assert defn.edges[0].operation == "HANDOFF"

    def test_defaults(self):
        """Default values for flow definition."""
        defn = FlowDefinition(name="test")
        assert defn.status == "active"
        assert defn.nodes == []
        assert defn.edges == []


class TestFlowListResponse:
    """Tests for FlowListResponse model."""

    def test_parse_list(self):
        """Flow list parses correctly."""
        data = {
            "flows": [{"name": "f1"}],
            "total": 1,
        }
        resp = FlowListResponse.model_validate(data)
        assert resp.total == 1
        assert resp.flows[0].name == "f1"
