"""
Tests for AgentsClient and AgentSession.

Uses mock to simulate server responses without a running server.
"""

import pytest
from unittest.mock import patch

from boring_os import BoringOS
from boring_os.agent_models import AgentResponse, AgentDefinition, AgentListResponse
from boring_os.sessions import AgentSession
from boring_os.exceptions import SessionError


# =============================================================================
# Sample data (inline to avoid conftest import issues)
# =============================================================================

SAMPLE_AGENT_RESPONSE = {
    "text": "I've looked into your account and found the overcharge.",
    "tool_calls": [{"tool": "get_billing_details", "result": {"balance": 150.0}}],
    "token_usage": {"input": 1200, "output": 350, "cost": 0.0085},
    "iterations": 2,
    "model_used": "claude-sonnet-4",
    "session_id": None,
    "request_id": "req-abc-123",
}

SAMPLE_AGENT_DEFINITION = {
    "name": "billing_agent",
    "description": "Handles billing inquiries",
    "model": "claude-sonnet-4",
    "tools": ["get_billing_details", "calculate_proration"],
    "prompt_id": "billing-prompt",
    "knowledge_sources": [],
    "max_iterations": 25,
    "max_total_tokens": 100000,
    "status": "active",
    "version": "1",
}

SAMPLE_AGENT_LIST = {
    "agents": [
        {"name": "billing_agent", "description": "Billing", "model": "claude-sonnet-4",
         "status": "active", "version": "1", "tools_count": 2},
        {"name": "roaming_agent", "description": "Roaming", "model": "gpt-4.1-mini",
         "status": "active", "version": "1", "tools_count": 3},
    ],
    "total": 2,
}

SAMPLE_SESSION_INFO = {"session_id": "ses-test-12345", "agent_name": "billing_agent"}


# =============================================================================
# AgentsClient Tests
# =============================================================================

class TestAgentsClientRun:
    """Tests for agents.run()."""

    def test_run_returns_agent_response(self, client):
        """run() returns a parsed AgentResponse."""
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_RESPONSE):
            response = client.agents.run("billing_agent", message="Why overcharged?")

        assert isinstance(response, AgentResponse)
        assert response.text == "I've looked into your account and found the overcharge."
        assert response.iterations == 2
        assert response.model_used == "claude-sonnet-4"
        assert response.request_id == "req-abc-123"

    def test_run_sends_correct_payload(self, client):
        """run() sends the right JSON body."""
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_RESPONSE) as mock:
            client.agents.run("billing_agent", message="Hello", session_id="ses-x")

        mock.assert_called_once()
        call_args = mock.call_args
        assert call_args[1]["json"]["agent"] == "billing_agent"
        assert call_args[1]["json"]["message"] == "Hello"
        assert call_args[1]["json"]["session_id"] == "ses-x"


class TestAgentsClientList:
    """Tests for agents.list()."""

    def test_list_returns_agent_list(self, client):
        """list() returns an AgentListResponse."""
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_LIST):
            result = client.agents.list()

        assert isinstance(result, AgentListResponse)
        assert result.total == 2
        assert result.agents[0].name == "billing_agent"


class TestAgentsClientGet:
    """Tests for agents.get()."""

    def test_get_returns_agent_definition(self, client):
        """get() returns a parsed AgentDefinition."""
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_DEFINITION):
            defn = client.agents.get("billing_agent")

        assert isinstance(defn, AgentDefinition)
        assert defn.name == "billing_agent"
        assert defn.model == "claude-sonnet-4"
        assert "get_billing_details" in defn.tools


class TestAgentsClientRegister:
    """Tests for agents.register()."""

    def test_register_returns_definition(self, client):
        """register() returns the created AgentDefinition."""
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_DEFINITION):
            defn = client.agents.register(
                name="billing_agent",
                model="claude-sonnet-4",
                tools=["get_billing_details"],
            )

        assert isinstance(defn, AgentDefinition)
        assert defn.name == "billing_agent"


class TestAgentsClientUpdate:
    """Tests for agents.update()."""

    def test_update_sends_changes(self, client):
        """update() sends the changes as JSON body."""
        updated = {**SAMPLE_AGENT_DEFINITION, "model": "claude-sonnet-4-5"}
        with patch.object(client, "_request", return_value=updated) as mock:
            defn = client.agents.update("billing_agent", model="claude-sonnet-4-5")

        assert defn.model == "claude-sonnet-4-5"
        mock.assert_called_once()
        assert mock.call_args[1]["json"]["model"] == "claude-sonnet-4-5"


class TestAgentsClientDeprecate:
    """Tests for agents.deprecate()."""

    def test_deprecate_calls_endpoint(self, client):
        """deprecate() calls POST /agents/{name}/deprecate."""
        with patch.object(client, "_request", return_value={}) as mock:
            client.agents.deprecate("old_agent")

        mock.assert_called_once()
        assert "/agents/old_agent/deprecate" in mock.call_args[0][1]


# =============================================================================
# AgentsClient Session Tests
# =============================================================================

class TestAgentsClientSession:
    """Tests for agents.create_session()."""

    def test_create_session_returns_session(self, client):
        """create_session() returns an AgentSession."""
        with patch.object(client, "_request", return_value=SAMPLE_SESSION_INFO):
            session = client.agents.create_session("billing_agent")

        assert isinstance(session, AgentSession)
        assert session.session_id == "ses-test-12345"
        assert session.agent_name == "billing_agent"


# =============================================================================
# AgentSession Tests
# =============================================================================

class TestAgentSession:
    """Tests for AgentSession directly."""

    def test_send_returns_response(self, client):
        """session.send() returns an AgentResponse."""
        session = AgentSession(
            client=client,
            session_id="ses-1",
            agent_name="billing_agent",
        )
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_RESPONSE):
            response = session.send("Why overcharged?")

        assert isinstance(response, AgentResponse)
        assert session.total_messages == 2  # user + assistant

    def test_send_accumulates_cost(self, client):
        """session.send() accumulates total_cost."""
        session = AgentSession(
            client=client,
            session_id="ses-1",
            agent_name="billing_agent",
        )
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_RESPONSE):
            session.send("msg1")
            session.send("msg2")

        assert session.total_messages == 4
        assert session.total_cost == pytest.approx(0.0085 * 2)

    def test_close_prevents_further_sends(self, client):
        """Sending after close raises SessionError."""
        session = AgentSession(
            client=client,
            session_id="ses-1",
            agent_name="billing_agent",
        )
        with patch.object(client, "_request", return_value={}):
            session.close()

        with pytest.raises(SessionError, match="closed"):
            session.send("should fail")

    def test_context_manager(self, client):
        """Session works as a context manager."""
        session = AgentSession(
            client=client,
            session_id="ses-1",
            agent_name="billing_agent",
        )
        with patch.object(client, "_request", return_value=SAMPLE_AGENT_RESPONSE):
            with session as s:
                s.send("hello")
            # After exiting context, session is closed
            assert session._closed

    def test_repr(self, client):
        """repr shows useful session info."""
        session = AgentSession(
            client=client,
            session_id="ses-1",
            agent_name="billing_agent",
        )
        r = repr(session)
        assert "ses-1" in r
        assert "billing_agent" in r
        assert "open" in r
