"""Unit tests for registry hydration in MCAClient."""

import pytest
import time
from unittest.mock import Mock, patch, MagicMock, ANY

from mca_sdk import MCAClient, MCAConfig
from mca_sdk.config.settings import SensitiveString
from mca_sdk.registry import ModelConfig
from mca_sdk.utils.exceptions import RegistryAuthError, RegistryConnectionError


class TestRegistryHydration:
    """Test MCAClient registry hydration flow."""

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_hydration_succeeds_with_valid_registry(
        self, mock_setup_providers, mock_registry_class
    ):
        """Test successful hydration from registry."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_registry_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={"latency_warn_ms": 500},
            extra_resource={"deployment.env": "test"},
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),  # meter_provider
            Mock(),  # logger_provider
            Mock(),  # tracer_provider
            Mock(),  # resource
            Mock(),  # metric_exporter
            Mock(),  # log_exporter
            Mock(),  # span_exporter
            Mock(),  # prometheus_reader
            Mock(),  # prometheus_shutdown
        )

        # Create client with registry config
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            registry_url="https://registry.example.com",
            registry_token="test-token",
        )
        client = MCAClient(config=config)

        # Verify registry client was created with correct parameters
        mock_registry_class.assert_called_once_with(
            url="https://registry.example.com",
            token=ANY,  # SensitiveString comparison fails
            timeout=5.0,
            cache_ttl_secs=600,
            refresh_interval_secs=600,  # Should pass refresh_interval_secs
            use_gcp_auth=ANY,
        )

        # Verify fetch was called
        mock_client_instance.fetch_model_config.assert_called_once_with(
            model_id="mdl-001",
            version="0.3.0",
        )

        # Verify thresholds were stored
        assert client.thresholds == {"latency_warn_ms": 500}

        # Clean up (no longer needed - refresh thread in RegistryClient)

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_hydration_fallback_on_registry_down(self, mock_setup_providers, mock_registry_class):
        """Test fallback to local config when registry unavailable."""
        # Mock registry client to raise connection error
        mock_client_instance = Mock()
        mock_client_instance.fetch_model_config.side_effect = RegistryConnectionError(
            "Connection failed"
        )
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client - should not raise, should use local config
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            team_name="local-team",
            registry_url="https://registry.example.com",
            registry_token="test-token",
        )
        client = MCAClient(config=config)

        # Verify client was created successfully
        assert client.config.service_name == "test-service"
        assert client.config.team_name == "local-team"
        assert client.thresholds == {}  # No registry thresholds

        # Clean up (no longer needed - refresh thread in RegistryClient)

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_hydration_fails_fast_on_auth_error(self, mock_setup_providers, mock_registry_class):
        """Test auth errors fail fast."""
        # Mock registry client to raise auth error
        mock_client_instance = Mock()
        mock_client_instance.fetch_model_config.side_effect = RegistryAuthError("Auth failed")
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client - should raise auth error
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            registry_url="https://registry.example.com",
            registry_token="bad-token",
        )

        with pytest.raises(RegistryAuthError):
            MCAClient(config=config)

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_extra_resource_merged_into_resource(self, mock_setup_providers, mock_registry_class):
        """Test extra_resource attributes merged into OTel Resource."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_registry_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={},
            extra_resource={
                "deployment.env": "production",
                "deployment.region": "us-east-1",
            },
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            registry_url="https://registry.example.com",
            registry_token="test-token",
        )
        client = MCAClient(config=config)

        # Verify setup_all_providers was called with extra_resource
        call_kwargs = mock_setup_providers.call_args[1]
        assert call_kwargs["extra_resource"] == {
            "deployment.env": "production",
            "deployment.region": "us-east-1",
        }

        # Clean up (no longer needed - refresh thread in RegistryClient)

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_deployment_config_fetched_when_configured(
        self, mock_setup_providers, mock_registry_class
    ):
        """Test deployment config is fetched when deployment_id provided."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_deployment_config = Mock()
        mock_deployment_config.resource_overrides = {"deployment.zone": "az-1"}
        mock_client_instance.fetch_deployment_config.return_value = mock_deployment_config

        mock_registry_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={},
            extra_resource={},
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client with deployment_id
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            deployment_id="dep-001",  # Set deployment ID
            registry_url="https://registry.example.com",
            registry_token="test-token",
        )
        client = MCAClient(config=config)

        # Verify deployment config was fetched
        mock_client_instance.fetch_deployment_config.assert_called_once_with("dep-001")

        # Clean up (no longer needed - refresh thread in RegistryClient)


class TestConfigPrecedence:
    """Test config precedence with registry integration."""

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_prefer_registry_true_overrides_local(self, mock_setup_providers, mock_registry_class):
        """Test registry overrides local when prefer_registry=True."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_registry_config = ModelConfig(
            service_name="registry-service",  # Different from local
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="registry-team",  # Different from local
            model_category="internal",
            model_type="regression",
            thresholds={},
            extra_resource={},
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client with prefer_registry=True (default)
        config = MCAConfig(
            service_name="local-service",
            model_id="mdl-001",
            team_name=None,  # No local team_name
            prefer_registry=True,
            registry_url="https://registry.example.com",
            registry_token="test-token",
        )
        client = MCAClient(config=config)

        # team_name should be from registry (local was None)
        assert client.config.team_name == "registry-team"

        # Clean up (no longer needed - refresh thread in RegistryClient)

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_identity_field_change_logs_warning(
        self, mock_setup_providers, mock_registry_class, caplog
    ):
        """Test warning logged when identity fields differ."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_registry_config = ModelConfig(
            service_name="different-service",  # Different from local
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={},
            extra_resource={},
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client
        config = MCAConfig(
            service_name="local-service",  # Different from registry
            model_id="mdl-001",
            prefer_registry=True,
            registry_url="https://registry.example.com",
            registry_token="test-token",
        )

        with caplog.at_level("WARNING"):
            client = MCAClient(config=config)

            # Should have warning about service_name mismatch
            assert any(
                "service_name in registry" in record.message and "differs" in record.message
                for record in caplog.records
            )

        # Clean up (no longer needed - refresh thread in RegistryClient)


class TestBackgroundRefresh:
    """Test background refresh functionality."""

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_refresh_thread_starts_when_configured(self, mock_setup_providers, mock_registry_class):
        """Test refresh thread starts with registry config (in RegistryClient)."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_registry_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={},
            extra_resource={},
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config

        # Mock refresh thread attributes (in RegistryClient)
        mock_refresh_thread = Mock()
        mock_refresh_thread.is_alive.return_value = True
        mock_client_instance._refresh_thread = mock_refresh_thread

        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client with short refresh interval for testing
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            registry_url="https://registry.example.com",
            registry_token="test-token",
            refresh_interval_secs=60,
        )
        client = MCAClient(config=config)

        # Verify RegistryClient was created with refresh enabled
        assert client._registry_client is not None
        # Refresh thread is managed by RegistryClient (not MCAClient)

        # Clean up
        client.shutdown()

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_refresh_thread_stops_on_shutdown(self, mock_setup_providers, mock_registry_class):
        """Test refresh thread stops cleanly on shutdown."""
        # Mock registry client
        mock_client_instance = Mock()
        mock_registry_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={},
            extra_resource={},
        )
        mock_client_instance.fetch_model_config.return_value = mock_registry_config

        # Mock refresh thread methods
        mock_refresh_thread = Mock()
        mock_refresh_thread.is_alive.return_value = True
        mock_client_instance._refresh_thread = mock_refresh_thread
        mock_client_instance.stop_refresh_thread = Mock()
        mock_client_instance.close = Mock()

        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_providers = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )
        mock_setup_providers.return_value = mock_providers

        # Create client
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            registry_url="https://registry.example.com",
            registry_token="test-token",
            refresh_interval_secs=1,
        )
        client = MCAClient(config=config)

        # Verify registry client refresh thread is running
        assert client._registry_client._refresh_thread.is_alive()

        # Shutdown
        client.shutdown()

        # Verify stop_refresh_thread was called
        mock_client_instance.stop_refresh_thread.assert_called_once_with(timeout=5.0)
        mock_client_instance.close.assert_called_once()

    @patch("mca_sdk.core.client.RegistryClient")
    @patch("mca_sdk.core.client.setup_all_providers")
    def test_refresh_updates_thresholds(self, mock_setup_providers, mock_registry_class):
        """Test that thresholds property returns updated values from registry cache."""
        # Mock registry client
        mock_client_instance = Mock()

        initial_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={"latency_warn_ms": 500},
            extra_resource={},
        )

        updated_config = ModelConfig(
            service_name="test-service",
            model_id="mdl-001",
            model_version="0.3.0",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
            thresholds={"latency_warn_ms": 1000},  # Updated threshold
            extra_resource={},
        )

        # First call returns initial, second call returns updated
        # (thresholds property calls fetch_model_config to get latest cached value)
        mock_client_instance.fetch_model_config.side_effect = [
            initial_config,  # Initial hydration
            initial_config,  # First thresholds property access
            updated_config,  # Second thresholds property access (after refresh)
        ]
        mock_registry_class.return_value = mock_client_instance

        # Mock providers
        mock_setup_providers.return_value = (
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
            Mock(),
        )

        # Create client
        config = MCAConfig(
            service_name="test-service",
            model_id="mdl-001",
            registry_url="https://registry.example.com",
            registry_token="test-token",
            refresh_interval_secs=60,
        )
        client = MCAClient(config=config)

        # Initial thresholds (calls fetch_model_config internally)
        assert client.thresholds == {"latency_warn_ms": 500}

        # Simulate refresh thread updating cache
        # Next access to thresholds property will get updated value
        thresholds_after_refresh = client.thresholds
        assert thresholds_after_refresh == {"latency_warn_ms": 1000}

        # Clean up
        client.shutdown()
