"""Tests for specter_cli.broker_client — broker API client."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest

from specter_cli.broker_client import BrokerClient, BrokerError


@pytest.fixture
def mock_httpx():
    """Mock httpx.Client for broker client tests."""
    with patch("specter_cli.broker_client.httpx.Client") as mock_cls:
        mock_client = MagicMock()
        mock_cls.return_value = mock_client
        yield mock_client


def _make_response(status_code: int, data: dict) -> MagicMock:
    """Create a mock httpx.Response."""
    resp = MagicMock()
    resp.status_code = status_code
    resp.json.return_value = data
    resp.text = json.dumps(data)
    return resp


class TestBrokerClientProvision:
    def test_provision_success(self, mock_httpx):
        resp_data = {
            "session_id": "sess_abc",
            "status": "provisioning",
            "gpu_type": "h100",
            "provider": "vastai",
        }
        mock_httpx.post.return_value = _make_response(202, resp_data)

        client = BrokerClient("sk-test123")
        result = client.provision("h100", "ssh-ed25519 AAAA...")

        assert result["session_id"] == "sess_abc"
        assert result["status"] == "provisioning"
        mock_httpx.post.assert_called_once()

    def test_provision_invalid_gpu(self, mock_httpx):
        resp_data = {
            "error": "invalid_gpu_type",
            "message": "Unknown GPU type 'v100'",
        }
        mock_httpx.post.return_value = _make_response(400, resp_data)

        client = BrokerClient("sk-test123")
        with pytest.raises(BrokerError) as exc_info:
            client.provision("v100", "ssh-ed25519 AAAA...")

        assert exc_info.value.status_code == 400
        assert "invalid_gpu_type" in str(exc_info.value)

    def test_provision_unauthorized(self, mock_httpx):
        resp_data = {"error": "unauthorized", "message": "Invalid API key"}
        mock_httpx.post.return_value = _make_response(401, resp_data)

        client = BrokerClient("sk-bad")
        with pytest.raises(BrokerError) as exc_info:
            client.provision("h100", "ssh-ed25519 AAAA...")

        assert exc_info.value.status_code == 401


class TestBrokerClientSession:
    def test_get_session_ready(self, mock_httpx):
        resp_data = {
            "session_id": "sess_abc",
            "status": "ready",
            "ssh": {"host": "10.0.0.1", "port": 22, "user": "root"},
        }
        mock_httpx.get.return_value = _make_response(200, resp_data)

        client = BrokerClient("sk-test123")
        result = client.get_session("sess_abc")

        assert result["status"] == "ready"
        assert result["ssh"]["host"] == "10.0.0.1"

    def test_get_session_not_found(self, mock_httpx):
        resp_data = {"error": "not_found", "message": "Unknown session"}
        mock_httpx.get.return_value = _make_response(404, resp_data)

        client = BrokerClient("sk-test123")
        with pytest.raises(BrokerError) as exc_info:
            client.get_session("sess_unknown")

        assert exc_info.value.status_code == 404


class TestBrokerClientDestroy:
    def test_destroy_success(self, mock_httpx):
        resp_data = {"session_id": "sess_abc", "status": "terminated"}
        mock_httpx.delete.return_value = _make_response(200, resp_data)

        client = BrokerClient("sk-test123")
        result = client.destroy_session("sess_abc")

        assert result["status"] == "terminated"

    def test_destroy_idempotent(self, mock_httpx):
        resp_data = {"session_id": "sess_abc", "status": "terminated"}
        mock_httpx.delete.return_value = _make_response(200, resp_data)

        client = BrokerClient("sk-test123")
        # Call twice — should not raise.
        client.destroy_session("sess_abc")
        client.destroy_session("sess_abc")


class TestBrokerClientHeartbeat:
    def test_heartbeat_success(self, mock_httpx):
        resp_data = {
            "session_id": "sess_abc",
            "status": "ready",
            "next_heartbeat_before": "2026-03-04T12:15:00Z",
        }
        mock_httpx.post.return_value = _make_response(200, resp_data)

        client = BrokerClient("sk-test123")
        result = client.heartbeat("sess_abc")

        assert result["status"] == "ready"

    def test_heartbeat_terminated_session(self, mock_httpx):
        resp_data = {"error": "conflict", "message": "Session already terminated"}
        mock_httpx.post.return_value = _make_response(409, resp_data)

        client = BrokerClient("sk-test123")
        with pytest.raises(BrokerError) as exc_info:
            client.heartbeat("sess_terminated")

        assert exc_info.value.status_code == 409


class TestBrokerClientContextManager:
    def test_context_manager(self, mock_httpx):
        with BrokerClient("sk-test123") as _client:
            pass
        mock_httpx.close.assert_called_once()
