"""Tests for JuniperCascorClient REST client."""

import pytest
import responses
from responses import matchers

from juniper_cascor_client import (
    JuniperCascorClient,
    JuniperCascorConflictError,
    JuniperCascorConnectionError,
    JuniperCascorNotFoundError,
    JuniperCascorServiceUnavailableError,
    JuniperCascorValidationError,
)


BASE_URL = "http://localhost:8200"
API_URL = f"{BASE_URL}/v1"


def _envelope(data):
    """Create a standard response envelope."""
    return {"status": "success", "data": data, "meta": {"timestamp": 1234567890.0, "version": "0.4.0"}}


def _error_response(code, message, status=400):
    """Create a standard error response."""
    return {"status": "error", "error": {"code": code, "message": message}, "meta": {"timestamp": 1234567890.0, "version": "0.4.0"}}


class TestClientInit:
    def test_default_url(self):
        client = JuniperCascorClient()
        assert client.base_url == "http://localhost:8200"
        assert client.api_url == "http://localhost:8200/v1"
        client.close()

    def test_custom_url(self):
        client = JuniperCascorClient("http://example.com:9000")
        assert client.base_url == "http://example.com:9000"
        client.close()

    def test_trailing_slash_stripped(self):
        client = JuniperCascorClient("http://example.com:9000/")
        assert client.base_url == "http://example.com:9000"
        client.close()

    def test_api_key_set_in_headers(self):
        client = JuniperCascorClient(api_key="test-key-123")
        assert client.session.headers["X-API-Key"] == "test-key-123"
        client.close()

    def test_context_manager(self):
        with JuniperCascorClient() as client:
            assert client is not None


class TestHealthEndpoints:
    @responses.activate
    def test_health_check(self):
        responses.add(responses.GET, f"{API_URL}/health", json=_envelope({"status": "ok", "version": "0.4.0"}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.health_check()
            assert result["data"]["status"] == "ok"

    @responses.activate
    def test_is_alive_true(self):
        responses.add(responses.GET, f"{API_URL}/health/live", json=_envelope({"status": "alive"}))
        with JuniperCascorClient(BASE_URL) as client:
            assert client.is_alive() is True

    @responses.activate
    def test_is_alive_false_on_error(self):
        responses.add(responses.GET, f"{API_URL}/health/live", body=ConnectionError("refused"))
        with JuniperCascorClient(BASE_URL) as client:
            assert client.is_alive() is False

    @responses.activate
    def test_is_ready_true(self):
        responses.add(responses.GET, f"{API_URL}/health/ready", json=_envelope({"network_loaded": True}))
        with JuniperCascorClient(BASE_URL) as client:
            assert client.is_ready() is True

    @responses.activate
    def test_is_ready_false(self):
        responses.add(responses.GET, f"{API_URL}/health/ready", json=_envelope({"network_loaded": False}))
        with JuniperCascorClient(BASE_URL) as client:
            assert client.is_ready() is False


class TestNetworkEndpoints:
    @responses.activate
    def test_create_network(self):
        responses.add(responses.POST, f"{API_URL}/network", json=_envelope({"input_size": 2, "output_size": 2, "hidden_units": 0, "uuid": "net-123"}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.create_network(input_size=2, output_size=2, learning_rate=0.01)
            assert result["data"]["uuid"] == "net-123"

    @responses.activate
    def test_get_network(self):
        responses.add(responses.GET, f"{API_URL}/network", json=_envelope({"input_size": 2}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_network()
            assert result["data"]["input_size"] == 2

    @responses.activate
    def test_delete_network(self):
        responses.add(responses.DELETE, f"{API_URL}/network", json=_envelope({"deleted": True}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.delete_network()
            assert result["data"]["deleted"] is True

    @responses.activate
    def test_get_topology(self):
        responses.add(responses.GET, f"{API_URL}/network/topology", json=_envelope({"nodes": [], "edges": []}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_topology()
            assert "nodes" in result["data"]

    @responses.activate
    def test_get_statistics(self):
        responses.add(responses.GET, f"{API_URL}/network/stats", json=_envelope({"total_params": 10}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_statistics()
            assert result["data"]["total_params"] == 10

    @responses.activate
    def test_create_network_conflict(self):
        responses.add(responses.POST, f"{API_URL}/network", json=_error_response("CONFLICT", "Network already exists"), status=409)
        with JuniperCascorClient(BASE_URL) as client:
            with pytest.raises(JuniperCascorConflictError):
                client.create_network(input_size=2, output_size=2, learning_rate=0.01)

    @responses.activate
    def test_get_network_not_found(self):
        responses.add(responses.GET, f"{API_URL}/network", json=_error_response("NOT_FOUND", "No network"), status=404)
        with JuniperCascorClient(BASE_URL) as client:
            with pytest.raises(JuniperCascorNotFoundError):
                client.get_network()


class TestTrainingEndpoints:
    @responses.activate
    def test_start_training(self):
        responses.add(responses.POST, f"{API_URL}/training/start", json=_envelope({"training_started": True, "epochs": 100}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.start_training(epochs=100)
            assert result["data"]["training_started"] is True

    @responses.activate
    def test_start_training_with_inline_data(self):
        responses.add(responses.POST, f"{API_URL}/training/start", json=_envelope({"training_started": True}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.start_training(inline_data={"train_x": [[1, 2]], "train_y": [[1, 0]]})
            assert result["data"]["training_started"] is True

    @responses.activate
    def test_stop_training(self):
        responses.add(responses.POST, f"{API_URL}/training/stop", json=_envelope({"training_stopped": True}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.stop_training()
            assert result["data"]["training_stopped"] is True

    @responses.activate
    def test_pause_training(self):
        responses.add(responses.POST, f"{API_URL}/training/pause", json=_envelope({"paused": True}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.pause_training()
            assert result["data"]["paused"] is True

    @responses.activate
    def test_resume_training(self):
        responses.add(responses.POST, f"{API_URL}/training/resume", json=_envelope({"resumed": True}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.resume_training()
            assert result["data"]["resumed"] is True

    @responses.activate
    def test_reset_training(self):
        responses.add(responses.POST, f"{API_URL}/training/reset", json=_envelope({"reset": True}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.reset_training()
            assert result["data"]["reset"] is True

    @responses.activate
    def test_get_training_status(self):
        responses.add(responses.GET, f"{API_URL}/training/status", json=_envelope({"training_active": True, "state_machine": {}}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_training_status()
            assert result["data"]["training_active"] is True

    @responses.activate
    def test_get_training_params(self):
        responses.add(responses.GET, f"{API_URL}/training/params", json=_envelope({"learning_rate": 0.01}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_training_params()
            assert result["data"]["learning_rate"] == 0.01


class TestMetricsEndpoints:
    @responses.activate
    def test_get_metrics(self):
        responses.add(responses.GET, f"{API_URL}/metrics", json=_envelope({"epoch": 10, "train_loss": 0.05}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_metrics()
            assert result["data"]["epoch"] == 10

    @responses.activate
    def test_get_metrics_history(self):
        responses.add(responses.GET, f"{API_URL}/metrics/history", json=_envelope([{"epoch": 1}, {"epoch": 2}]))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_metrics_history(count=50)
            assert len(result["data"]) == 2

    @responses.activate
    def test_get_metrics_not_found(self):
        responses.add(responses.GET, f"{API_URL}/metrics", json=_error_response("NOT_FOUND", "No network"), status=404)
        with JuniperCascorClient(BASE_URL) as client:
            with pytest.raises(JuniperCascorNotFoundError):
                client.get_metrics()


class TestDataEndpoints:
    @responses.activate
    def test_get_dataset(self):
        responses.add(responses.GET, f"{API_URL}/dataset", json=_envelope({"train_shape": [500, 2]}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_dataset()
            assert result["data"]["train_shape"] == [500, 2]

    @responses.activate
    def test_get_decision_boundary(self):
        responses.add(responses.GET, f"{API_URL}/decision-boundary", json=_envelope({"resolution": 50, "predictions": []}))
        with JuniperCascorClient(BASE_URL) as client:
            result = client.get_decision_boundary(resolution=50)
            assert result["data"]["resolution"] == 50


class TestErrorHandling:
    @responses.activate
    def test_validation_error_400(self):
        responses.add(responses.POST, f"{API_URL}/network", json=_error_response("VALIDATION_ERROR", "Invalid"), status=400)
        with JuniperCascorClient(BASE_URL) as client:
            with pytest.raises(JuniperCascorValidationError):
                client.create_network(input_size=-1)

    @responses.activate
    def test_validation_error_422(self):
        responses.add(responses.POST, f"{API_URL}/network", json=_error_response("VALIDATION_ERROR", "Invalid"), status=422)
        with JuniperCascorClient(BASE_URL) as client:
            with pytest.raises(JuniperCascorValidationError):
                client.create_network()

    @responses.activate
    def test_service_unavailable_503(self):
        responses.add(responses.GET, f"{API_URL}/network", json=_error_response("SERVICE_UNAVAILABLE", "Not ready"), status=503)
        with JuniperCascorClient(BASE_URL) as client:
            with pytest.raises(JuniperCascorServiceUnavailableError):
                client.get_network()

    def test_connection_refused(self):
        with JuniperCascorClient("http://localhost:19999") as client:
            with pytest.raises(JuniperCascorConnectionError):
                client.health_check()
