import pytest
from unittest.mock import patch


@pytest.fixture(autouse=True)
def mock_registry_client():
    with patch("mca_sdk.registry.client.RegistryClient._fetch_model_config_internal") as mock_fetch:
        # Prevent TypeError: '>=' not supported between instances of 'MagicMock' and 'int'
        from mca_sdk.registry.models import ModelConfig

        mock_fetch.return_value = ModelConfig(
            model_id="test",
            model_version="1.0",
            service_name="test-service",
            team_name="test-team",
            model_category="internal",
            model_type="regression",
        )
        yield mock_fetch


"""Unit tests for registry background refresh thread functionality.

Tests cover thread lifecycle, refresh interval, error handling, and graceful shutdown.
"""

import logging
import threading
import time
from unittest.mock import Mock, MagicMock

import pytest

from mca_sdk.registry.client import RegistryClient
from mca_sdk.registry.models import ModelConfig
from mca_sdk.utils.exceptions import (
    RegistryAuthError,
    RegistryConnectionError,
    RegistryError,
)


@pytest.fixture
def mock_model_config():
    """Create a mock ModelConfig for testing."""
    return ModelConfig(
        service_name="test-service",
        model_id="mdl-001",
        model_version="1.0.0",
        team_name="test-team",
        model_category="internal",
        model_type="classification",
        thresholds={"accuracy": 0.95},
        extra_resource={},
    )


@pytest.mark.allow_refresh_thread
class TestRefreshThreadLifecycle:
    """Test background refresh thread lifecycle management."""

    def test_thread_starts_when_registry_url_configured(self, mock_model_config):
        """Test that background thread starts when registry_url is provided."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                token="test-token",
                refresh_interval_secs=60,
            )

            # Thread should be started
            assert client._refresh_thread is not None
            assert client._refresh_thread.is_alive()
            assert client._refresh_thread.daemon is True  # Must be daemon

            # Cleanup
            client.stop_refresh_thread()

    def test_thread_not_started_when_refresh_disabled(self):
        """Test that thread is not started when refresh_interval_secs=0."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                token="test-token",
                refresh_interval_secs=0,  # Disabled
            )

            # Thread should NOT be started
            assert client._refresh_thread is None

    def test_stop_thread_gracefully(self, mock_model_config):
        """Test graceful thread shutdown."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=60,
            )

            # Stop the thread
            client.stop_refresh_thread(timeout=2.0)

            # Thread should be stopped
            assert not client._refresh_thread.is_alive()

    def test_multiple_start_attempts_logged(self, caplog, mock_model_config):
        """Test that multiple start attempts log a warning."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=60,
            )

            # Try to start thread again (should log warning)
            with caplog.at_level(logging.WARNING):
                client._start_refresh_thread()

            assert "already running" in caplog.text.lower()

            # Cleanup
            client.stop_refresh_thread()

    def test_stop_when_thread_not_running_no_error(self):
        """Test that stopping when thread not running is safe (idempotent)."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=0,  # No thread started
            )

            # Should not raise error
            client.stop_refresh_thread()


@pytest.mark.allow_refresh_thread
class TestRefreshInterval:
    """Test configurable refresh interval."""

    def test_default_refresh_interval(self):
        """Test default refresh interval is 600 seconds."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
            )

            assert client._refresh_interval == 600

            # Cleanup if thread started
            if client._refresh_thread:
                client.stop_refresh_thread()

    def test_custom_refresh_interval(self):
        """Test custom refresh interval."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=300,
            )

            assert client._refresh_interval == 300

            # Cleanup
            client.stop_refresh_thread()

    def test_refresh_interval_validation_minimum(self):
        """Test that refresh interval must be >= 30 seconds."""
        with patch("mca_sdk.registry.client.requests.Session"):
            with pytest.raises(ValueError, match="must be >= 30 seconds"):
                RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=10,  # Too low
                )


@pytest.mark.allow_refresh_thread
class TestRefreshErrorHandling:
    """Test error handling in refresh loop."""

    def test_auth_error_stops_thread(self, caplog, mock_model_config):
        """Test that RegistryAuthError stops the refresh thread."""
        with patch("mca_sdk.registry.client.MIN_REFRESH_INTERVAL_SECS", 1):
            with patch("mca_sdk.registry.client.requests.Session"):
                client = RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=1,  # Short interval for testing
                )

                # Add a model to track so refresh loop has something to refresh
                client._fetched_models["mdl-001"] = "1.0.0"

                # Mock fetch_model_config to raise auth error
                with patch.object(
                    client,
                    "fetch_model_config",
                    side_effect=RegistryAuthError("401 Unauthorized"),
                ):
                    # Wait for thread to encounter auth error and stop
                    time.sleep(2)

                    # Thread should have stopped
                    assert not client._refresh_thread.is_alive()
                    assert "auth failed" in caplog.text.lower()

    def test_connection_error_continues_thread(self, caplog, mock_model_config):
        """Test that RegistryConnectionError logs warning and continues."""
        with patch("mca_sdk.registry.client.MIN_REFRESH_INTERVAL_SECS", 1):
            with patch("mca_sdk.registry.client.requests.Session"):
                client = RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=1,
                )

                # Add a model to track so refresh loop has something to refresh
                client._fetched_models["mdl-001"] = "1.0.0"

                # Track how many times fetch is called
                call_count = {"value": 0}

                def mock_fetch_with_error(*args, **kwargs):
                    call_count["value"] += 1
                    if call_count["value"] <= 2:
                        raise RegistryConnectionError("Connection refused")
                    # After 2 errors, succeed to stop the test
                    client.stop_refresh_thread()
                    return mock_model_config

                with patch.object(client, "fetch_model_config", side_effect=mock_fetch_with_error):
                    # Wait for retries
                    time.sleep(3)

                    # Thread should have continued past the connection errors
                    assert call_count["value"] >= 2
                    assert "continuing with other models" in caplog.text.lower()

    def test_registry_error_continues_thread(self, caplog, mock_model_config):
        """Test that RegistryError logs warning and continues."""
        with patch("mca_sdk.registry.client.MIN_REFRESH_INTERVAL_SECS", 1):
            with patch("mca_sdk.registry.client.requests.Session"):
                client = RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=1,
                )

                # Add a model to track so refresh loop has something to refresh
                client._fetched_models["mdl-001"] = "1.0.0"

                call_count = {"value": 0}

                def mock_fetch_with_error(*args, **kwargs):
                    call_count["value"] += 1
                    if call_count["value"] <= 2:
                        raise RegistryError("500 Internal Server Error")
                    client.stop_refresh_thread()
                    return mock_model_config

                with patch.object(client, "fetch_model_config", side_effect=mock_fetch_with_error):
                    time.sleep(3)

                    assert call_count["value"] >= 2
                    assert "continuing with other models" in caplog.text.lower()

    def test_unexpected_exception_continues_thread(self, caplog, mock_model_config):
        """Test that unexpected exceptions log error and continue (defensive)."""
        with patch("mca_sdk.registry.client.MIN_REFRESH_INTERVAL_SECS", 1):
            with patch("mca_sdk.registry.client.requests.Session"):
                client = RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=1,
                )

                # Add a model to track so refresh loop has something to refresh
                client._fetched_models["mdl-001"] = "1.0.0"

                call_count = {"value": 0}

                def mock_fetch_with_error(*args, **kwargs):
                    call_count["value"] += 1
                    if call_count["value"] <= 2:
                        raise RuntimeError("Unexpected error")
                    client.stop_refresh_thread()
                    return mock_model_config

                with patch.object(client, "fetch_model_config", side_effect=mock_fetch_with_error):
                    with caplog.at_level(logging.ERROR):
                        time.sleep(3)

                        assert call_count["value"] >= 2
                        assert "unexpected error" in caplog.text.lower()


@pytest.mark.allow_refresh_thread
class TestGracefulShutdown:
    """Test graceful thread shutdown scenarios."""

    def test_normal_stop_within_timeout(self):
        """Test thread stops normally within timeout."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=60,
            )

            start_time = time.time()
            client.stop_refresh_thread(timeout=5.0)
            elapsed = time.time() - start_time

            # Should stop quickly (not hit timeout)
            assert elapsed < 5.0
            assert not client._refresh_thread.is_alive()

    def test_thread_timeout_logs_warning(self, caplog):
        """Test that thread not stopping within timeout logs warning."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=60,
            )

            # Mock thread.join to simulate timeout
            original_thread = client._refresh_thread

            def mock_join(timeout=None):
                time.sleep(timeout)  # Simulate not finishing

            with patch.object(original_thread, "join", side_effect=mock_join):
                with patch.object(original_thread, "is_alive", return_value=True):
                    with caplog.at_level(logging.WARNING):
                        client.stop_refresh_thread(timeout=0.5)

                        assert "did not stop within" in caplog.text.lower()

    def test_already_stopped_idempotent(self):
        """Test that stopping an already stopped thread is safe."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=60,
            )

            # Stop once
            client.stop_refresh_thread()

            # Stop again - should not raise error
            client.stop_refresh_thread()


@pytest.mark.allow_refresh_thread
class TestRefreshStats:
    """Test refresh statistics tracking."""

    def test_refresh_stats_initialized(self):
        """Test that refresh stats are initialized."""
        with patch("mca_sdk.registry.client.requests.Session"):
            client = RegistryClient(
                url="https://registry.example.com",
                refresh_interval_secs=60,
            )

            stats = client.get_refresh_stats()

            assert "last_refresh_time" in stats
            assert "refresh_count" in stats
            assert "refresh_errors" in stats
            assert "is_running" in stats
            assert stats["is_running"] is True

            client.stop_refresh_thread()

    def test_refresh_count_increments(self, mock_model_config):
        """Test that refresh_count increments on successful refresh."""
        with patch("mca_sdk.registry.client.MIN_REFRESH_INTERVAL_SECS", 1):
            with patch("mca_sdk.registry.client.requests.Session"):
                client = RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=1,
                )

                # Add a model to track so refresh loop has something to refresh
                client._fetched_models["mdl-001"] = "1.0.0"

                # Mock successful fetch
                with patch.object(client, "fetch_model_config", return_value=mock_model_config):
                    # Wait for at least 2 refreshes
                    time.sleep(2.5)

                    stats = client.get_refresh_stats()
                    assert stats["refresh_count"] >= 2
                    assert stats["last_refresh_time"] > 0

                client.stop_refresh_thread()

    def test_refresh_errors_increments(self, mock_model_config):
        """Test that refresh_errors increments on failed refresh."""
        with patch("mca_sdk.registry.client.MIN_REFRESH_INTERVAL_SECS", 1):
            with patch("mca_sdk.registry.client.requests.Session"):
                client = RegistryClient(
                    url="https://registry.example.com",
                    refresh_interval_secs=1,
                )

                # Add a model to track so refresh loop has something to refresh
                client._fetched_models["mdl-001"] = "1.0.0"

                call_count = {"value": 0}

                def mock_fetch_with_error(*args, **kwargs):
                    call_count["value"] += 1
                    if call_count["value"] <= 2:
                        raise RegistryConnectionError("Connection failed")
                    client.stop_refresh_thread()
                    return mock_model_config

                with patch.object(client, "fetch_model_config", side_effect=mock_fetch_with_error):
                    time.sleep(3)

                    stats = client.get_refresh_stats()
                    assert stats["refresh_errors"] >= 2

                client.stop_refresh_thread()
