from unittest.mock import patch, MagicMock
import pytest
from ezop import Agent
from ezop.models import AgentModel, AgentVersion, AgentRun


AGENT_RESP = {"success": True, "error": None, "data": {
    "id": "agent-uuid-123",
    "name": "support-bot",
    "owner": "growth-team",
    "runtime": "langchain",
    "description": "Handles support tickets",
    "default_permissions": ["read:tickets"],
}}

VERSION_RESP = {"success": True, "error": None, "data": {
    "id": "version-uuid-456",
    "version": "v0.3",
    "permissions": ["read:tickets", "write:replies"],
    "changelog": "Initial release",
}}

RUN_RESP = {"success": True, "error": None, "data": {
    "id": "run-uuid-789",
    "status": "running",
}}


def make_agent():
    with patch("ezop.client.EzopClient.register_agent", return_value=AGENT_RESP), \
         patch("ezop.client.EzopClient.create_version", return_value=VERSION_RESP):
        return Agent.init(
            name="support-bot",
            owner="growth-team",
            version="v0.3",
            runtime="langchain",
            description="Handles support tickets",
            default_permissions=["read:tickets"],
            permissions=["read:tickets", "write:replies"],
            changelog="Initial release",
        )


class TestAgentInit:
    def test_returns_agent_instance(self):
        agent = make_agent()
        assert isinstance(agent, Agent)

    def test_model_populated(self):
        agent = make_agent()
        assert agent.model.id == "agent-uuid-123"
        assert agent.model.name == "support-bot"
        assert agent.model.owner == "growth-team"
        assert agent.model.runtime == "langchain"
        assert agent.model.description == "Handles support tickets"
        assert agent.model.default_permissions == ["read:tickets"]

    def test_version_populated(self):
        agent = make_agent()
        assert agent.version.id == "version-uuid-456"
        assert agent.version.agent_id == "agent-uuid-123"
        assert agent.version.version == "v0.3"
        assert agent.version.permissions == ["read:tickets", "write:replies"]
        assert agent.version.changelog == "Initial release"

    def test_no_active_run_on_init(self):
        agent = make_agent()
        assert agent.current_run is None

    def test_calls_register_agent_with_correct_payload(self):
        with patch("ezop.client.EzopClient.register_agent", return_value=AGENT_RESP) as mock_register, \
             patch("ezop.client.EzopClient.create_version", return_value=VERSION_RESP):
            Agent.init(name="support-bot", owner="growth-team", version="v0.3", runtime="langchain",
                       description="Handles support tickets", default_permissions=["read:tickets"])
            mock_register.assert_called_once_with({
                "name": "support-bot",
                "owner": "growth-team",
                "runtime": "langchain",
                "description": "Handles support tickets",
                "default_permissions": ["read:tickets"],
            })

    def test_calls_create_version_with_correct_payload(self):
        with patch("ezop.client.EzopClient.register_agent", return_value=AGENT_RESP), \
             patch("ezop.client.EzopClient.create_version", return_value=VERSION_RESP) as mock_version:
            Agent.init(name="support-bot", owner="growth-team", version="v0.3", runtime="langchain",
                       permissions=["read:tickets"], changelog="Initial release")
            mock_version.assert_called_once_with("agent-uuid-123", {
                "version": "v0.3",
                "permissions": ["read:tickets"],
                "changelog": "Initial release",
            })

    def test_optional_fields_default_to_empty(self):
        with patch("ezop.client.EzopClient.register_agent", return_value={**AGENT_RESP, "data": {**AGENT_RESP["data"], "default_permissions": None}}), \
             patch("ezop.client.EzopClient.create_version", return_value={**VERSION_RESP, "data": {**VERSION_RESP["data"], "permissions": None}}):
            agent = Agent.init(name="support-bot", owner="growth-team", version="v0.3", runtime="langchain")
            assert agent.model.default_permissions == []
            assert agent.version.permissions == []

    def test_raises_on_api_error(self):
        with patch("ezop.client.EzopClient.register_agent", side_effect=Exception("Ezop API error: 401")):
            with pytest.raises(Exception, match="Ezop API error"):
                Agent.init(name="support-bot", owner="growth-team", version="v0.3", runtime="langchain")


class TestAgentRuns:
    def test_start_run_returns_agent_run(self):
        agent = make_agent()
        with patch("ezop.client.EzopClient.start_run", return_value=RUN_RESP):
            run = agent.start_run()
        assert isinstance(run, AgentRun)
        assert run.id == "run-uuid-789"
        assert run.status == "running"
        assert run.agent_id == "agent-uuid-123"
        assert run.version_id == "version-uuid-456"

    def test_start_run_sets_current_run(self):
        agent = make_agent()
        with patch("ezop.client.EzopClient.start_run", return_value=RUN_RESP):
            agent.start_run()
        assert agent.current_run is not None
        assert agent.current_run.id == "run-uuid-789"

    def test_end_run_updates_status(self):
        agent = make_agent()
        with patch("ezop.client.EzopClient.start_run", return_value=RUN_RESP):
            agent.start_run()
        end_resp = {"success": True, "error": None, "data": {"status": "completed", "total_tokens": 500, "total_cost": 0.01, "metadata": None}}
        with patch("ezop.client.EzopClient.end_run", return_value=end_resp):
            run = agent.end_run(status="completed", total_tokens=500, total_cost=0.01)
        assert run.status == "completed"
        assert run.total_tokens == 500
        assert run.total_cost == 0.01

    def test_end_run_failed_status(self):
        agent = make_agent()
        with patch("ezop.client.EzopClient.start_run", return_value=RUN_RESP):
            agent.start_run()
        end_resp = {"success": True, "error": None, "data": {"status": "failed", "total_tokens": None, "total_cost": None, "metadata": {"error": "timeout"}, "message": "Rate limit exceeded"}}
        with patch("ezop.client.EzopClient.end_run", return_value=end_resp):
            run = agent.end_run(status="failed", metadata={"error": "timeout"}, message="Rate limit exceeded")
        assert run.status == "failed"
        assert run.metadata == {"error": "timeout"}
        assert run.message == "Rate limit exceeded"

    def test_end_run_calls_client_with_correct_args(self):
        agent = make_agent()
        with patch("ezop.client.EzopClient.start_run", return_value=RUN_RESP):
            agent.start_run()
        end_resp = {"success": True, "error": None, "data": {"status": "completed", "total_tokens": 100, "total_cost": 0.005, "metadata": {"k": "v"}, "message": None}}
        with patch("ezop.client.EzopClient.end_run", return_value=end_resp) as mock_end:
            agent.end_run(status="completed", total_tokens=100, total_cost=0.005, metadata={"k": "v"})
            mock_end.assert_called_once_with(
                "run-uuid-789",
                status="completed",
                total_tokens=100,
                total_cost=0.005,
                metadata={"k": "v"},
                message=None,
            )

    def test_end_run_without_start_raises(self):
        agent = make_agent()
        with pytest.raises(RuntimeError, match="No active run"):
            agent.end_run()
