"""
Tests for the DeepLTranslationProvider.
"""

import socket
import pytest
from concurrent.futures import TimeoutError as FuturesTimeoutError
from unittest.mock import patch, MagicMock

from services.providers.deepl_provider import (
    DeepLTranslationProvider,
    DeepLProviderError,
    get_deepl_provider,
    register_deepl_provider,
    DEEPL_QUOTA_EXCEEDED,
    DEEPL_INVALID_KEY,
    DEEPL_NETWORK_ERROR,
    DEEPL_UNSUPPORTED_LANGUAGE,
    DEEPL_TEXT_TOO_LONG,
)
from services.providers.schemas import TranslationRequest, TranslationResponse


class TestDeepLProviderError:
    """Tests for DeepLProviderError exception."""

    def test_error_creation(self):
        """Test error creation with all fields."""
        error = DeepLProviderError(
            code=DEEPL_INVALID_KEY,
            message="Invalid API key",
            details={"provider": "deepl"},
        )

        assert error.code == DEEPL_INVALID_KEY
        assert error.message == "Invalid API key"
        assert error.details == {"provider": "deepl"}

    def test_error_to_dict(self):
        """Test error serialization."""
        error = DeepLProviderError(
            code=DEEPL_QUOTA_EXCEEDED,
            message="Quota exceeded",
            details={"reset_at": "2024-01-16T00:00:00Z"},
        )

        result = error.to_dict()

        assert result["error"] == DEEPL_QUOTA_EXCEEDED
        assert result["message"] == "Quota exceeded"
        assert result["details"]["reset_at"] == "2024-01-16T00:00:00Z"

    def test_error_to_dict_no_details(self):
        """Test error serialization without details."""
        error = DeepLProviderError(
            code=DEEPL_NETWORK_ERROR,
            message="Network error",
        )

        result = error.to_dict()

        assert result["error"] == DEEPL_NETWORK_ERROR
        assert result["message"] == "Network error"
        assert "details" not in result


class TestDeepLTranslationProvider:
    """Tests for DeepLTranslationProvider."""

    @pytest.fixture
    def provider(self):
        """Create a DeepL provider instance with Pro key."""
        return DeepLTranslationProvider(
            api_key="test-pro-key-12345",
            use_cache=False,
        )

    @pytest.fixture
    def provider_free(self):
        """Create a DeepL provider instance with Free tier key."""
        return DeepLTranslationProvider(
            api_key="test-free-key:fx",
            use_cache=False,
        )

    def test_init_requires_api_key(self):
        """Test that initialization requires API key."""
        with pytest.raises(ValueError, match="API key is required"):
            DeepLTranslationProvider(api_key="")

    def test_get_name(self, provider):
        """Test provider name."""
        assert provider.get_name() == "deepl"

    def test_detect_api_type_pro(self, provider):
        """Test Pro API key detection."""
        assert provider._api_type == "pro"

    def test_detect_api_type_free(self, provider_free):
        """Test Free API key detection."""
        assert provider_free._api_type == "free"

    def test_get_api_url_pro(self, provider):
        """Test Pro API URL."""
        url = provider._get_api_url()
        assert url == "https://api.deepl.com/v2/translate"

    def test_get_api_url_free(self, provider_free):
        """Test Free API URL."""
        url = provider_free._get_api_url()
        assert url == "https://api-free.deepl.com/v2/translate"

    def test_normalize_language_code_uppercase(self, provider):
        """Test language code normalization to uppercase."""
        assert provider._normalize_language_code("en") == "EN-US"
        assert provider._normalize_language_code("fr") == "FR"
        assert provider._normalize_language_code("pt") == "PT-BR"

    def test_normalize_language_code_preserves_variant(self, provider):
        """Test that language variants are preserved."""
        assert provider._normalize_language_code("en-gb") == "EN-GB"
        assert provider._normalize_language_code("en-us") == "EN-US"
        assert provider._normalize_language_code("pt-pt") == "PT-PT"

    def test_normalize_language_code_auto(self, provider):
        """Test auto language code handling."""
        assert provider._normalize_language_code("auto") == ""
        assert provider._normalize_language_code("") == ""

    def test_is_language_supported(self, provider):
        """Test language support checking."""
        assert provider._is_language_supported("en") is True
        assert provider._is_language_supported("fr") is True
        assert provider._is_language_supported("EN-US") is True
        assert provider._is_language_supported("XX") is False

    def test_translate_text_empty(self, provider):
        """Test translating empty text."""
        request = TranslationRequest(text="", target_language="fr")
        response = provider.translate_text(request)

        assert response.translated_text == ""
        assert response.provider_name == "deepl"
        assert response.from_cache is False

    def test_translate_text_whitespace(self, provider):
        """Test translating whitespace-only text."""
        request = TranslationRequest(text="   ", target_language="fr")
        response = provider.translate_text(request)

        assert response.translated_text == "   "

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_translate_text_success(self, mock_get_translator, provider):
        """Test successful translation."""
        mock_translator = MagicMock()
        mock_translator.translate.return_value = "Bonjour"
        mock_get_translator.return_value = mock_translator

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.translated_text == "Bonjour"
        assert response.provider_name == "deepl"
        assert response.from_cache is False

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_translate_text_with_source_language(self, mock_get_translator, provider):
        """Test translation with explicit source language."""
        mock_translator = MagicMock()
        mock_translator.translate.return_value = "Bonjour"
        mock_get_translator.return_value = mock_translator

        request = TranslationRequest(
            text="Hello", target_language="fr", source_language="en"
        )
        response = provider.translate_text(request)

        assert response.translated_text == "Bonjour"

    def test_translate_text_same_language_skip(self, provider):
        """Test that translation is skipped when source == target."""
        request = TranslationRequest(
            text="Hello",
            target_language="en",
            source_language="en",
        )
        response = provider.translate_text(request)

        assert response.translated_text == "Hello"
        assert response.from_cache is False

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_translate_text_error_fallback(self, mock_get_translator, provider):
        """Test that translation errors return original text and structured error."""
        mock_get_translator.side_effect = Exception("API Error")

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.translated_text == "Hello"
        assert response.provider_name == "deepl"
        assert response.error is not None
        assert response.error_code is not None

    def test_translate_batch_empty(self, provider):
        """Test batch translation with empty list."""
        responses = provider.translate_batch([])
        assert responses == []

    @patch.object(DeepLTranslationProvider, "translate_text")
    def test_translate_batch(self, mock_translate, provider):
        """Test batch translation."""
        mock_translate.side_effect = [
            TranslationResponse(translated_text="Bonjour", provider_name="deepl"),
            TranslationResponse(translated_text="Monde", provider_name="deepl"),
        ]

        requests = [
            TranslationRequest(text="Hello", target_language="fr"),
            TranslationRequest(text="World", target_language="fr"),
        ]
        responses = provider.translate_batch(requests)

        assert len(responses) == 2
        assert responses[0].translated_text == "Bonjour"
        assert responses[1].translated_text == "Monde"

    def test_health_check(self, provider):
        """Test health check."""
        status = provider.health_check()

        assert status.name == "deepl"
        assert isinstance(status.available, bool)
        assert status.latency_ms is not None


class TestDeepLErrorCodes:
    """Tests for DeepL error code handling."""

    @pytest.fixture
    def provider(self):
        """Create a DeepL provider instance."""
        return DeepLTranslationProvider(
            api_key="test-key-12345",
            use_cache=False,
            max_retries=0,
        )

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_quota_exceeded_error(self, mock_get_translator, provider):
        """Test quota exceeded error handling."""
        mock_get_translator.side_effect = Exception("quota exceeded")

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.error_code == DEEPL_QUOTA_EXCEEDED
        assert "quota" in response.error.lower() or "dépassé" in response.error.lower()

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_invalid_key_error(self, mock_get_translator, provider):
        """Test invalid API key error handling."""
        mock_get_translator.side_effect = Exception("403 Forbidden - invalid auth")

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.error_code == DEEPL_INVALID_KEY

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_unsupported_language_error(self, mock_get_translator, provider):
        """Test unsupported language error handling."""
        mock_get_translator.side_effect = Exception("language not supported")

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.error_code == DEEPL_UNSUPPORTED_LANGUAGE

    def test_text_too_long_error(self, provider):
        """Test text too long error handling."""
        long_text = "x" * (200 * 1024)
        request = TranslationRequest(text=long_text, target_language="fr")
        response = provider.translate_text(request)

        assert response.error_code == DEEPL_TEXT_TOO_LONG
        assert response.error_details is not None
        assert "text_length" in response.error_details or "max_length" in response.error_details

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_timeout_exception_maps_to_network_error(self, mock_get_translator, provider):
        """Test that socket.timeout and FuturesTimeoutError map to DEEPL_NETWORK_ERROR."""
        mock_get_translator.side_effect = FuturesTimeoutError()

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.error_code == DEEPL_NETWORK_ERROR
        assert response.error is not None

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_socket_timeout_maps_to_network_error(self, mock_get_translator, provider):
        """Test that socket.timeout maps to DEEPL_NETWORK_ERROR."""
        mock_get_translator.side_effect = socket.timeout("timed out")

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.error_code == DEEPL_NETWORK_ERROR


class TestDeepLProviderCaching:
    """Tests for DeepL provider caching functionality."""

    @pytest.fixture
    def mock_cache(self):
        """Create a mock cache."""
        cache = MagicMock()
        cache.get.return_value = None
        return cache

    def test_cache_hit(self, mock_cache):
        """Test that cache hits return cached result."""
        mock_cache.get.return_value = "Cached Translation"

        provider = DeepLTranslationProvider(
            api_key="test-key-12345",
            use_cache=True,
        )
        provider._cache = mock_cache

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.translated_text == "Cached Translation"
        assert response.from_cache is True

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_cache_set_on_miss(self, mock_get_translator, mock_cache):
        """Test that translations are cached on miss."""
        mock_translator = MagicMock()
        mock_translator.translate.return_value = "Bonjour"
        mock_get_translator.return_value = mock_translator

        provider = DeepLTranslationProvider(
            api_key="test-key-12345",
            use_cache=True,
        )
        provider._cache = mock_cache

        request = TranslationRequest(text="Hello", target_language="fr")
        provider.translate_text(request)

        mock_cache.set.assert_called_once()


class TestDeepLProviderRetry:
    """Tests for DeepL provider retry logic."""

    @pytest.fixture
    def provider(self):
        """Create a DeepL provider with retry enabled."""
        return DeepLTranslationProvider(
            api_key="test-key-12345",
            use_cache=False,
            max_retries=2,
            retry_delay=0.01,
        )

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_retry_on_network_error(self, mock_get_translator, provider):
        """Test that network errors trigger retry."""
        mock_translator = MagicMock()
        mock_translator.translate.side_effect = [
            Exception("timeout"),
            "Bonjour",
        ]
        mock_get_translator.return_value = mock_translator

        request = TranslationRequest(text="Hello", target_language="fr")
        response = provider.translate_text(request)

        assert response.translated_text == "Bonjour"
        assert mock_translator.translate.call_count == 2

    @patch("services.providers.deepl_provider.DeepLTranslationProvider._get_translator")
    def test_no_retry_on_invalid_key(self, mock_get_translator, provider):
        """Test that invalid key errors do not trigger retry."""
        mock_translator = MagicMock()
        mock_translator.translate.side_effect = Exception("401 invalid auth")
        mock_get_translator.return_value = mock_translator

        request = TranslationRequest(text="Hello", target_language="fr")
        provider.translate_text(request)

        assert mock_translator.translate.call_count == 1


class TestDeepLProviderSingleton:
    """Tests for DeepL provider singleton functions."""

    def test_get_deepl_provider_no_config(self):
        """Test get_deepl_provider returns None without config."""
        import services.providers.deepl_provider as deepl_module

        deepl_module._provider_instance = None

        with patch("services.providers.config.ProvidersConfig") as mock_config:
            mock_config.DEEPL_API_KEY = ""
            result = deepl_module.get_deepl_provider()

            assert result is None

    def test_get_deepl_provider_with_config(self):
        """Test get_deepl_provider creates instance with config."""
        import services.providers.deepl_provider as deepl_module

        deepl_module._provider_instance = None

        with patch("services.providers.config.ProvidersConfig") as mock_config:
            mock_config.DEEPL_API_KEY = "test-key:fx"
            mock_config.DEEPL_TIMEOUT = 30
            mock_config.DEEPL_MAX_RETRIES = 3
            mock_config.DEEPL_RETRY_DELAY = 1.0

            provider = deepl_module.get_deepl_provider()

            assert provider is not None
            assert provider._api_type == "free"

            deepl_module._provider_instance = None


class TestDeepLRegistryIntegration:
    """Tests for DeepL provider registry integration."""

    def test_register_deepl_provider(self):
        """Test provider registration."""
        from services.providers.registry import registry

        registry.unregister("deepl")

        with patch("services.providers.deepl_provider.get_deepl_provider") as mock_get:
            mock_provider = MagicMock()
            mock_get.return_value = mock_provider

            from services.providers.deepl_provider import register_deepl_provider

            result = register_deepl_provider()

            assert result == mock_provider
            assert "deepl" in registry
            registry.unregister("deepl")

    def test_register_deepl_provider_no_config(self):
        """Test provider registration when not configured."""
        from services.providers.registry import registry

        registry.unregister("deepl")

        with patch("services.providers.deepl_provider.get_deepl_provider") as mock_get:
            mock_get.return_value = None

            from services.providers.deepl_provider import register_deepl_provider

            result = register_deepl_provider()

            assert result is None
            assert "deepl" not in registry


class TestLegacyDeepLAdapter:
    """Tests for LegacyDeepLAdapter."""

    def test_adapter_not_configured(self):
        """Test adapter when DeepL is not configured."""
        with patch("services.providers.deepl_provider.get_deepl_provider") as mock_get:
            mock_get.return_value = None

            from services.providers.deepl_provider import LegacyDeepLAdapter

            adapter = LegacyDeepLAdapter()

            with pytest.raises(Exception) as exc_info:
                adapter.translate("Hello", "fr")

            assert "not configured" in str(exc_info.value).lower()
