"""Integration tests for registry client version parameter handling.

Tests validate that the registry client correctly sends version parameters
to the API and handles responses appropriately, using mocked HTTP responses.
"""

import pytest
import responses
from mca_sdk.registry.client import RegistryClient, DEFAULT_MODEL_VERSION
from mca_sdk.registry.models import ModelConfig
from mca_sdk.utils.exceptions import RegistryConfigNotFoundError


@pytest.fixture
def mock_registry_url():
    """Fixture providing test registry URL."""
    return "https://registry.example.com"


@pytest.fixture
def sample_model_response():
    """Fixture providing sample model config response."""
    return {
        "service_name": "test-service",
        "model_id": "test-model-id",
        "model_version": "1.2.3",
        "team_name": "test-team",
        "model_category": "internal",
        "model_type": "regression",
    }


class TestVersionParameterIntegration:
    """Integration tests for version parameter in API requests."""

    @responses.activate
    def test_fetch_without_version_sends_latest_parameter(
        self, mock_registry_url, sample_model_response
    ):
        """Test that fetch_model_config without version sends version=latest to API."""
        # Mock the API endpoint
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=sample_model_response,
            status=200,
            match=[responses.matchers.query_param_matcher({"version": DEFAULT_MODEL_VERSION})],
        )

        # Create client and fetch config
        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)
        config = client.fetch_model_config("test-model-id")

        # Verify response
        assert isinstance(config, ModelConfig)
        assert config.model_id == "test-model-id"
        assert config.model_version == "1.2.3"

        # Verify the request was made with correct parameters
        assert len(responses.calls) == 1
        assert "version=latest" in responses.calls[0].request.url

    @responses.activate
    def test_fetch_with_explicit_version_sends_that_version(
        self, mock_registry_url, sample_model_response
    ):
        """Test that fetch_model_config with explicit version sends that version to API."""
        # Update response to match requested version
        response_data = sample_model_response.copy()
        response_data["model_version"] = "2.0.0"

        # Mock the API endpoint
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=response_data,
            status=200,
            match=[responses.matchers.query_param_matcher({"version": "2.0.0"})],
        )

        # Create client and fetch config
        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)
        config = client.fetch_model_config("test-model-id", version="2.0.0")

        # Verify response
        assert config.model_version == "2.0.0"

        # Verify the request was made with correct parameters
        assert len(responses.calls) == 1
        assert "version=2.0.0" in responses.calls[0].request.url

    @responses.activate
    def test_fetch_latest_version_not_found_raises_clear_error(self, mock_registry_url):
        """Test that 404 for 'latest' version raises clear error message."""
        # Mock 404 response
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/nonexistent-model",
            json={"error": "Model not found"},
            status=404,
            match=[responses.matchers.query_param_matcher({"version": DEFAULT_MODEL_VERSION})],
        )

        # Create client and attempt fetch
        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryConfigNotFoundError) as exc_info:
            client.fetch_model_config("nonexistent-model")

        # Verify error message contains actionable information
        error_msg = str(exc_info.value)
        assert "nonexistent-model" in error_msg
        assert DEFAULT_MODEL_VERSION in error_msg
        assert "registered" in error_msg.lower() or "exists" in error_msg.lower()

    @responses.activate
    def test_cache_key_matches_api_request_version(self, mock_registry_url, sample_model_response):
        """Test that cache key semantics match actual API request version parameter."""
        # Mock the API endpoint
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=sample_model_response,
            status=200,
            match=[responses.matchers.query_param_matcher({"version": DEFAULT_MODEL_VERSION})],
        )

        # Create client
        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        # First call - should hit API
        config1 = client.fetch_model_config("test-model-id")
        assert len(responses.calls) == 1

        # Second call - should hit cache (not make new API request)
        config2 = client.fetch_model_config("test-model-id")
        assert len(responses.calls) == 1  # Still 1, no new request

        # Configs should be identical
        assert config1.model_id == config2.model_id
        assert config1.model_version == config2.model_version

    @responses.activate
    def test_different_versions_use_different_cache_keys(
        self, mock_registry_url, sample_model_response
    ):
        """Test that different versions are cached separately."""
        # Mock for "latest" version
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=sample_model_response,
            status=200,
            match=[responses.matchers.query_param_matcher({"version": DEFAULT_MODEL_VERSION})],
        )

        # Mock for explicit version "2.0.0"
        response_v2 = sample_model_response.copy()
        response_v2["model_version"] = "2.0.0"
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=response_v2,
            status=200,
            match=[responses.matchers.query_param_matcher({"version": "2.0.0"})],
        )

        # Create client
        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        # Fetch default version
        config_latest = client.fetch_model_config("test-model-id")
        assert config_latest.model_version == "1.2.3"

        # Fetch explicit version
        config_v2 = client.fetch_model_config("test-model-id", version="2.0.0")
        assert config_v2.model_version == "2.0.0"

        # Should have made 2 API calls (different cache keys)
        assert len(responses.calls) == 2

        # Fetch default again - should use cache
        config_latest_cached = client.fetch_model_config("test-model-id")
        assert len(responses.calls) == 2  # Still 2, cached

        # Fetch v2 again - should use cache
        config_v2_cached = client.fetch_model_config("test-model-id", version="2.0.0")
        assert len(responses.calls) == 2  # Still 2, cached

    @responses.activate
    def test_api_returns_different_resolved_version(self, mock_registry_url, sample_model_response):
        """Test that when requesting 'latest', API may return a specific version."""
        # When requesting "latest", API resolves to "3.1.4"
        response_data = sample_model_response.copy()
        response_data["model_version"] = "3.1.4"

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=response_data,
            status=200,
            match=[responses.matchers.query_param_matcher({"version": DEFAULT_MODEL_VERSION})],
        )

        # Create client and fetch
        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)
        config = client.fetch_model_config("test-model-id")

        # Verify that resolved version is returned
        assert config.model_version == "3.1.4"

        # Verify request was made with "latest"
        assert "version=latest" in responses.calls[0].request.url


class TestThreadSafetyIntegration:
    """Integration tests for thread-safety of registry client operations."""

    @responses.activate
    def test_concurrent_fetches_with_same_version(self, mock_registry_url, sample_model_response):
        """Test that concurrent fetches of same model/version are thread-safe."""
        import threading
        import time

        # Mock slow API response
        import json

        def slow_response(request):
            time.sleep(0.1)  # Simulate network delay
            return (200, {}, json.dumps(sample_model_response))

        responses.add_callback(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            callback=slow_response,
            content_type="application/json",
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        results = []
        errors = []

        def fetch_config():
            try:
                config = client.fetch_model_config("test-model-id")
                results.append(config)
            except Exception as e:
                errors.append(e)

        # Start multiple threads
        threads = [threading.Thread(target=fetch_config) for _ in range(5)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # Verify no errors occurred
        assert len(errors) == 0, f"Errors occurred: {errors}"

        # All results should be present
        assert len(results) == 5

        # All results should be identical
        for result in results:
            assert result.model_id == "test-model-id"
            assert result.model_version == "1.2.3"


class TestNegativePathIntegration:
    """Integration tests for error handling and negative paths."""

    @responses.activate
    def test_network_timeout_raises_connection_error(self, mock_registry_url):
        """Test that network timeout raises RegistryConnectionError."""
        import requests
        from mca_sdk.utils.exceptions import RegistryConnectionError

        # Mock timeout by raising requests.Timeout
        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            body=requests.exceptions.Timeout("Connection timeout"),
        )

        client = RegistryClient(url=mock_registry_url, timeout=1.0, refresh_interval_secs=0)

        with pytest.raises(RegistryConnectionError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert (
            "timeout" in error_msg.lower()
            or "timed out" in error_msg.lower()
            or "connect" in error_msg.lower()
        )

    @responses.activate
    def test_http_500_internal_server_error(self, mock_registry_url):
        """Test that HTTP 500 raises RegistryError with clear message."""
        from mca_sdk.utils.exceptions import RegistryError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json={"error": "Internal server error"},
            status=500,
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert "500" in error_msg or "server error" in error_msg.lower()

    @responses.activate
    def test_http_429_rate_limit_error(self, mock_registry_url):
        """Test that HTTP 429 raises RegistryError indicating rate limiting."""
        from mca_sdk.utils.exceptions import RegistryError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json={"error": "Rate limit exceeded"},
            status=429,
            headers={"Retry-After": "60"},
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert "429" in error_msg or "rate limit" in error_msg.lower()

    @responses.activate
    def test_malformed_json_response(self, mock_registry_url):
        """Test that malformed JSON response raises clear error."""
        from mca_sdk.utils.exceptions import RegistryError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            body="not valid json {{{",
            status=200,
            content_type="application/json",
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert "json" in error_msg.lower() or "parse" in error_msg.lower()

    @responses.activate
    def test_missing_required_fields_in_response(self, mock_registry_url):
        """Test that response missing required fields raises validation error."""
        from mca_sdk.utils.exceptions import RegistryError

        # Response missing required fields like service_name, model_id
        incomplete_response = {
            "team_name": "test-team",
            # Missing: service_name, model_id, model_version, model_category, model_type
        }

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json=incomplete_response,
            status=200,
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        # Should indicate validation or parsing error
        assert (
            "validation" in error_msg.lower()
            or "field" in error_msg.lower()
            or "parse" in error_msg.lower()
        )

    @responses.activate
    def test_http_401_unauthorized(self, mock_registry_url):
        """Test that HTTP 401 raises RegistryAuthError."""
        from mca_sdk.utils.exceptions import RegistryAuthError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json={"error": "Unauthorized"},
            status=401,
        )

        client = RegistryClient(
            url=mock_registry_url, token="invalid-token", refresh_interval_secs=0
        )

        with pytest.raises(RegistryAuthError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert (
            "401" in error_msg or "unauthorized" in error_msg.lower() or "auth" in error_msg.lower()
        )

    @responses.activate
    def test_http_403_forbidden(self, mock_registry_url):
        """Test that HTTP 403 raises RegistryAuthError."""
        from mca_sdk.utils.exceptions import RegistryAuthError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            json={"error": "Forbidden - insufficient permissions"},
            status=403,
        )

        client = RegistryClient(
            url=mock_registry_url, token="valid-but-insufficient", refresh_interval_secs=0
        )

        with pytest.raises(RegistryAuthError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert (
            "403" in error_msg
            or "forbidden" in error_msg.lower()
            or "permission" in error_msg.lower()
        )

    @responses.activate
    def test_connection_error_network_unreachable(self, mock_registry_url):
        """Test that connection error raises RegistryConnectionError."""
        import requests
        from mca_sdk.utils.exceptions import RegistryConnectionError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            body=requests.exceptions.ConnectionError("Network unreachable"),
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryConnectionError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert "connect" in error_msg.lower() or "network" in error_msg.lower()

    @responses.activate
    def test_empty_response_body(self, mock_registry_url):
        """Test that empty response body raises clear error."""
        from mca_sdk.utils.exceptions import RegistryError

        responses.add(
            responses.GET,
            f"{mock_registry_url}/models/test-model-id",
            body="",
            status=200,
            content_type="application/json",
        )

        client = RegistryClient(url=mock_registry_url, refresh_interval_secs=0)

        with pytest.raises(RegistryError) as exc_info:
            client.fetch_model_config("test-model-id")

        error_msg = str(exc_info.value)
        assert (
            "empty" in error_msg.lower()
            or "json" in error_msg.lower()
            or "parse" in error_msg.lower()
        )


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
