"""Unit tests for monitored_provider.py - Metrics collection, health checks, monitoring integration."""

import time
from unittest.mock import MagicMock, patch

import pytest

from src.core.providers.base_provider import Loader
from src.core.providers.monitored_provider import (
    MonitoredProviderWrapper,
    wrap_provider,
)


class MockProvider(Loader):
    """Mock provider for testing the monitoring wrapper."""

    def __init__(self, site_key: str = "mock.provider"):
        self._site_key = site_key
        self._search_result = []
        self._is_language_result = True
        self._download_result = True
        self._title = "Mock Title"
        self._season_episodes = {1: 12}
        self.raise_on_search = False
        self.raise_on_download = False

    def subscribe_download_progress(self, handler):
        pass

    def unsubscribe_download_progress(self, handler):
        pass

    def search(self, word):
        if self.raise_on_search:
            raise ConnectionError("Search failed")
        return self._search_result

    def is_language(self, season, episode, key, language="German Dub"):
        return self._is_language_result

    def download(
        self, base_directory, serie_folder, season, episode, key,
        language="German Dub", progress_callback=None
    ):
        if self.raise_on_download:
            raise ConnectionError("Download failed")
        return self._download_result

    def get_site_key(self):
        return self._site_key

    def get_title(self, key):
        return self._title

    def get_season_episode_count(self, slug):
        return self._season_episodes


class ConcreteMonitoredWrapper(MonitoredProviderWrapper):
    """Concrete subclass adding the missing abstract methods."""

    def subscribe_download_progress(self, handler):
        pass

    def unsubscribe_download_progress(self, handler):
        pass


@pytest.fixture
def mock_provider():
    """Create a mock provider instance."""
    return MockProvider()


@pytest.fixture
def mock_health_monitor():
    """Create a mock health monitor."""
    monitor = MagicMock()
    return monitor


@pytest.fixture
def monitored_wrapper(mock_provider, mock_health_monitor):
    """Create a monitored wrapper with mock health monitor."""
    with patch(
        "src.core.providers.monitored_provider.get_health_monitor",
        return_value=mock_health_monitor,
    ):
        wrapper = ConcreteMonitoredWrapper(
            provider=mock_provider,
            enable_monitoring=True,
        )
    return wrapper


class TestMonitoredProviderWrapperInit:
    """Test MonitoredProviderWrapper initialization."""

    def test_wrapper_stores_provider(self, mock_provider):
        """Wrapper should store the wrapped provider."""
        with patch(
            "src.core.providers.monitored_provider.get_health_monitor"
        ):
            wrapper = ConcreteMonitoredWrapper(mock_provider)
        assert wrapper._provider is mock_provider

    def test_wrapper_monitoring_enabled_by_default(self, mock_provider):
        """Monitoring should be enabled by default."""
        with patch(
            "src.core.providers.monitored_provider.get_health_monitor"
        ):
            wrapper = ConcreteMonitoredWrapper(mock_provider)
        assert wrapper._enable_monitoring is True

    def test_wrapper_monitoring_can_be_disabled(self, mock_provider):
        """Monitoring can be disabled on init."""
        wrapper = ConcreteMonitoredWrapper(
            mock_provider, enable_monitoring=False
        )
        assert wrapper._enable_monitoring is False
        assert wrapper._health_monitor is None

    def test_wrapped_provider_property(self, monitored_wrapper, mock_provider):
        """wrapped_provider property should return the underlying provider."""
        assert monitored_wrapper.wrapped_provider is mock_provider


class TestMonitoredSearch:
    """Test search with monitoring."""

    def test_search_delegates_to_provider(self, monitored_wrapper, mock_provider):
        """search() should delegate to wrapped provider."""
        mock_provider._search_result = [{"title": "Test"}]
        result = monitored_wrapper.search("test")
        assert result == [{"title": "Test"}]

    def test_search_records_success_metric(
        self, monitored_wrapper, mock_health_monitor
    ):
        """Successful search should record a success metric."""
        monitored_wrapper.search("test")
        mock_health_monitor.record_request.assert_called_once()
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["success"] is True
        assert call_kwargs["provider_name"] == "mock.provider"

    def test_search_records_failure_metric(
        self, monitored_wrapper, mock_provider, mock_health_monitor
    ):
        """Failed search should record a failure metric."""
        mock_provider.raise_on_search = True
        with pytest.raises(ConnectionError):
            monitored_wrapper.search("test")
        mock_health_monitor.record_request.assert_called_once()
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["success"] is False

    def test_search_propagates_exception(
        self, monitored_wrapper, mock_provider
    ):
        """Exception from provider should propagate through wrapper."""
        mock_provider.raise_on_search = True
        with pytest.raises(ConnectionError, match="Search failed"):
            monitored_wrapper.search("test")


class TestMonitoredIsLanguage:
    """Test is_language with monitoring."""

    def test_is_language_delegates(self, monitored_wrapper, mock_provider):
        """is_language should delegate to wrapped provider."""
        mock_provider._is_language_result = True
        result = monitored_wrapper.is_language(1, 1, "key")
        assert result is True

    def test_is_language_records_metric(
        self, monitored_wrapper, mock_health_monitor
    ):
        """is_language should record metric."""
        monitored_wrapper.is_language(1, 1, "key")
        mock_health_monitor.record_request.assert_called_once()
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["success"] is True


class TestMonitoredDownload:
    """Test download with monitoring."""

    def test_download_delegates_to_provider(
        self, monitored_wrapper, mock_provider
    ):
        """download should delegate to wrapped provider."""
        mock_provider._download_result = True
        result = monitored_wrapper.download(
            "/base", "folder", 1, 1, "key"
        )
        assert result is True

    def test_download_records_success(
        self, monitored_wrapper, mock_health_monitor
    ):
        """Successful download should record success metric."""
        monitored_wrapper.download("/base", "folder", 1, 1, "key")
        mock_health_monitor.record_request.assert_called_once()
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["success"] is True

    def test_download_records_failure(
        self, monitored_wrapper, mock_provider, mock_health_monitor
    ):
        """Failed download should record failure metric."""
        mock_provider.raise_on_download = True
        with pytest.raises(ConnectionError):
            monitored_wrapper.download("/base", "folder", 1, 1, "key")
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["success"] is False

    def test_download_tracks_bytes(
        self, monitored_wrapper, mock_provider, mock_health_monitor
    ):
        """Download with progress callback should track bytes."""
        progress_data = {"downloaded": 5000}

        def mock_download(
            base_directory, serie_folder, season, episode, key,
            language="German Dub", progress_callback=None
        ):
            if progress_callback:
                progress_callback("progress", progress_data)
            return True

        mock_provider.download = mock_download
        callback = MagicMock()
        monitored_wrapper.download(
            "/base", "folder", 1, 1, "key",
            progress_callback=callback
        )
        callback.assert_called_once_with("progress", progress_data)


class TestMonitoredGetTitle:
    """Test get_title with monitoring."""

    def test_get_title_delegates(self, monitored_wrapper, mock_provider):
        """get_title should delegate and return title."""
        mock_provider._title = "Naruto"
        result = monitored_wrapper.get_title("naruto")
        assert result == "Naruto"

    def test_get_title_records_metric(
        self, monitored_wrapper, mock_health_monitor
    ):
        """get_title should record metric."""
        monitored_wrapper.get_title("test")
        mock_health_monitor.record_request.assert_called_once()


class TestMonitoredGetSiteKey:
    """Test get_site_key delegation."""

    def test_get_site_key_delegates(self, monitored_wrapper):
        """get_site_key should return wrapped provider's site key."""
        assert monitored_wrapper.get_site_key() == "mock.provider"


class TestMonitoredGetSeasonEpisodeCount:
    """Test get_season_episode_count with monitoring."""

    def test_delegates_correctly(self, monitored_wrapper, mock_provider):
        """Should delegate and return season/episode data."""
        mock_provider._season_episodes = {1: 24, 2: 12}
        result = monitored_wrapper.get_season_episode_count("test")
        assert result == {1: 24, 2: 12}

    def test_records_metric(self, monitored_wrapper, mock_health_monitor):
        """Should record metric for season/episode count call."""
        monitored_wrapper.get_season_episode_count("test")
        mock_health_monitor.record_request.assert_called_once()


class TestRecordOperation:
    """Test _record_operation method."""

    def test_no_recording_when_monitoring_disabled(self, mock_provider):
        """Should not record when monitoring is disabled."""
        wrapper = ConcreteMonitoredWrapper(
            mock_provider, enable_monitoring=False
        )
        # This should not raise even without health monitor
        wrapper._record_operation(
            "test_op", time.time(), True
        )

    def test_records_elapsed_time(
        self, monitored_wrapper, mock_health_monitor
    ):
        """Should calculate and record elapsed time."""
        start = time.time() - 0.1  # 100ms ago
        monitored_wrapper._record_operation(
            "test_op", start, True
        )
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["response_time_ms"] > 0

    def test_records_error_message(
        self, monitored_wrapper, mock_health_monitor
    ):
        """Should record error message on failure."""
        monitored_wrapper._record_operation(
            "test_op", time.time(), False, error_message="test error"
        )
        call_kwargs = mock_health_monitor.record_request.call_args[1]
        assert call_kwargs["error_message"] == "test error"


class TestWrapProviderFunction:
    """Test the wrap_provider convenience function."""

    def test_wrap_creates_monitored_wrapper(self, mock_provider):
        """wrap_provider should return MonitoredProviderWrapper."""
        with patch(
            "src.core.providers.monitored_provider.get_health_monitor"
        ):
            # wrap_provider returns MonitoredProviderWrapper which can't be
            # instantiated directly due to missing abstract methods.
            # This tests that wrap_provider raises the expected error.
            with pytest.raises(TypeError):
                result = wrap_provider(mock_provider)

    def test_wrap_with_monitoring_disabled(self, mock_provider):
        """wrap_provider with monitoring disabled."""
        # MonitoredProviderWrapper is abstract, so wrap_provider can't
        # create it directly. This tests the expected behavior.
        with pytest.raises(TypeError):
            result = wrap_provider(mock_provider, enable_monitoring=False)
