import pytest
from unittest.mock import MagicMock, patch

from edpconfigprovider._ConfigProvider import ConfigProvider
from edpconfigprovider._FallbackConfigProvider import FallbackConfigProvider
from edpconfigprovider.exceptions import NoConfigurationException


class TestFallbackConfigProvider:
    def _make_provider(self, config=None, exception=None):
        provider = MagicMock(spec=ConfigProvider)
        if exception:
            provider.get_configuration.side_effect = exception
        else:
            provider.get_configuration.return_value = config or {}
        return provider

    def test_single_provider_succeeds(self):
        provider = self._make_provider(config={"key": "value"})
        fallback = FallbackConfigProvider([provider])

        result = fallback.get_configuration()

        assert result == {"key": "value"}
        provider.get_configuration.assert_called_once()

    def test_first_fails_second_succeeds(self):
        first = self._make_provider(exception=NoConfigurationException("first"))
        second = self._make_provider(config={"fallback": "data"})
        fallback = FallbackConfigProvider([first, second])

        result = fallback.get_configuration()

        assert result == {"fallback": "data"}
        first.get_configuration.assert_called_once()
        second.get_configuration.assert_called_once()

    def test_all_providers_fail_raises_last_exception(self):
        first = self._make_provider(exception=NoConfigurationException("first"))
        second = self._make_provider(exception=NoConfigurationException("second"))
        fallback = FallbackConfigProvider([first, second])

        with pytest.raises(NoConfigurationException, match="second"):
            fallback.get_configuration()

    def test_non_no_configuration_exception_propagates(self):
        first = self._make_provider(exception=RuntimeError("unexpected"))
        second = self._make_provider(config={"key": "value"})
        fallback = FallbackConfigProvider([first, second])

        with pytest.raises(RuntimeError, match="unexpected"):
            fallback.get_configuration()

        second.get_configuration.assert_not_called()

    def test_empty_providers_raises_value_error(self):
        with pytest.raises(ValueError, match="at least one provider"):
            FallbackConfigProvider([])

    def test_three_providers_first_two_fail(self):
        first = self._make_provider(exception=NoConfigurationException("first"))
        second = self._make_provider(exception=NoConfigurationException("second"))
        third = self._make_provider(config={"final": "result"})
        fallback = FallbackConfigProvider([first, second, third])

        result = fallback.get_configuration()

        assert result == {"final": "result"}

    def test_logs_warning_on_fallback(self):
        first = self._make_provider(exception=NoConfigurationException("first"))
        second = self._make_provider(config={"key": "value"})
        fallback = FallbackConfigProvider([first, second])

        with patch("edpconfigprovider._FallbackConfigProvider.logger") as mock_logger:
            fallback.get_configuration()
            mock_logger.warning.assert_called_once()