"""Tests for the i18n (internationalization) and l10n (localization) utilities."""

from __future__ import annotations

import json
from datetime import date, datetime
from pathlib import Path
from unittest.mock import MagicMock

import pytest
from fastapi.testclient import TestClient

from app.utils.i18n import (
    DEFAULT_LANGUAGE,
    SUPPORTED_LANGUAGE_CODES,
    SUPPORTED_LANGUAGES,
    _parse_accept_language,
    detect_language,
    format_date,
    format_datetime,
    format_number,
    get_language_info,
    get_suggested_languages,
    reload_translations,
    translate,
)

# ---------------------------------------------------------------------------
# Translation file integrity
# ---------------------------------------------------------------------------


class TestTranslationFiles:
    """Verify that all translation JSON files are valid and complete."""

    @pytest.fixture(autouse=True)
    def _clear_cache(self) -> None:
        """Clear translation cache before each test."""
        reload_translations()

    @pytest.mark.unit
    def test_all_translation_files_exist(self) -> None:
        """Every supported language must have a corresponding JSON file."""
        translations_dir = Path(__file__).resolve().parent.parent / "frontend" / "translations"
        for lang in SUPPORTED_LANGUAGES:
            filepath = translations_dir / f"{lang['code']}.json"
            assert filepath.is_file(), f"Missing translation file for {lang['code']}"

    @pytest.mark.unit
    def test_all_translation_files_are_valid_json(self) -> None:
        """All translation files must be parseable JSON."""
        translations_dir = Path(__file__).resolve().parent.parent / "frontend" / "translations"
        for lang in SUPPORTED_LANGUAGES:
            filepath = translations_dir / f"{lang['code']}.json"
            data = json.loads(filepath.read_text(encoding="utf-8"))
            assert isinstance(data, dict), f"{lang['code']}.json must be a dict"
            assert len(data) > 0, f"{lang['code']}.json must not be empty"


# ---------------------------------------------------------------------------
# Core translate() function
# ---------------------------------------------------------------------------


class TestTranslate:
    """Tests for the translate() function."""

    @pytest.fixture(autouse=True)
    def _clear_cache(self) -> None:
        reload_translations()

    @pytest.mark.unit
    def test_translate_known_key_returns_non_empty_string(self) -> None:
        """A valid key should resolve to a non-empty string (not the key itself)."""
        result = translate("nav.dashboard", "en")
        assert isinstance(result, str)
        assert result != ""
        assert result != "nav.dashboard"

    @pytest.mark.unit
    def test_translate_non_english_locale_returns_value(self) -> None:
        """Non-English locales should return a non-empty translated value."""
        for locale in ("de", "fr", "zh"):
            result = translate("nav.dashboard", locale)
            assert isinstance(result, str)
            assert result != ""
            assert result != "nav.dashboard"

    @pytest.mark.unit
    def test_translate_fallback_to_english(self) -> None:
        """Unknown locale falls back to English translation."""
        result = translate("nav.dashboard", "xx")
        english = translate("nav.dashboard", "en")
        assert result == english

    @pytest.mark.unit
    def test_translate_missing_key_returns_key(self) -> None:
        """Missing key falls back to the key itself."""
        result = translate("nonexistent.key", "en")
        assert result == "nonexistent.key"

    @pytest.mark.unit
    def test_translate_none_locale_uses_default(self) -> None:
        """None locale defaults to English."""
        result = translate("nav.dashboard", None)
        english = translate("nav.dashboard", "en")
        assert result == english

    @pytest.mark.unit
    def test_translate_with_kwargs(self) -> None:
        """Placeholders should be interpolated via kwargs."""
        result = translate("footer.copyright", "en", year="2025")
        assert "2025" in result
        assert result != "footer.copyright"

    @pytest.mark.unit
    def test_translate_with_kwargs_german(self) -> None:
        """Placeholder interpolation in German."""
        result = translate("language.changed", "de", language="English")
        assert "English" in result
        assert result != "language.changed"


# ---------------------------------------------------------------------------
# Accept-Language header parsing
# ---------------------------------------------------------------------------


class TestParseAcceptLanguage:
    """Tests for parsing the Accept-Language HTTP header."""

    @pytest.mark.unit
    def test_simple_language(self) -> None:
        assert _parse_accept_language("de") == "de"

    @pytest.mark.unit
    def test_language_with_region(self) -> None:
        assert _parse_accept_language("de-DE") == "de"

    @pytest.mark.unit
    def test_multiple_languages_quality(self) -> None:
        result = _parse_accept_language("fr;q=0.9, de;q=1.0, en;q=0.8")
        assert result == "de"

    @pytest.mark.unit
    def test_unsupported_language_fallback(self) -> None:
        result = _parse_accept_language("xx, yy")
        assert result is None

    @pytest.mark.unit
    def test_empty_header(self) -> None:
        assert _parse_accept_language("") is None

    @pytest.mark.unit
    def test_complex_accept_language(self) -> None:
        header = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
        result = _parse_accept_language(header)
        assert result == "zh"


# ---------------------------------------------------------------------------
# Language detection
# ---------------------------------------------------------------------------


class TestDetectLanguage:
    """Tests for detecting language from request context."""

    @pytest.mark.unit
    def test_session_preference_takes_priority(self) -> None:
        request = MagicMock()
        request.session = {"preferred_language": "de"}
        request.cookies = {}
        request.headers = {}
        assert detect_language(request) == "de"

    @pytest.mark.unit
    def test_cookie_fallback(self) -> None:
        request = MagicMock()
        request.session = {}
        request.cookies = {"docuelevate_lang": "fr"}
        request.headers = {}
        assert detect_language(request) == "fr"

    @pytest.mark.unit
    def test_accept_language_fallback(self) -> None:
        request = MagicMock()
        request.session = {}
        request.cookies = {}
        request.headers = {"accept-language": "es-ES,es;q=0.9"}
        assert detect_language(request) == "es"

    @pytest.mark.unit
    def test_default_fallback(self) -> None:
        request = MagicMock()
        request.session = {}
        request.cookies = {}
        request.headers = {}
        assert detect_language(request) == DEFAULT_LANGUAGE

    @pytest.mark.unit
    def test_invalid_session_language_ignored(self) -> None:
        request = MagicMock()
        request.session = {"preferred_language": "invalid"}
        request.cookies = {"docuelevate_lang": "it"}
        request.headers = {}
        assert detect_language(request) == "it"


# ---------------------------------------------------------------------------
# Localization helpers
# ---------------------------------------------------------------------------


class TestL10nFormatters:
    """Tests for locale-aware formatting functions."""

    @pytest.mark.unit
    def test_format_date_english(self) -> None:
        d = date(2025, 3, 15)
        result = format_date(d, "en")
        assert "March" in result
        assert "15" in result
        assert "2025" in result

    @pytest.mark.unit
    def test_format_date_german(self) -> None:
        d = date(2025, 3, 15)
        result = format_date(d, "de")
        assert "15." in result
        assert "2025" in result

    @pytest.mark.unit
    def test_format_date_short(self) -> None:
        d = date(2025, 3, 15)
        result = format_date(d, "en", short=True)
        assert result == "03/15/2025"

    @pytest.mark.unit
    def test_format_date_short_german(self) -> None:
        d = date(2025, 3, 15)
        result = format_date(d, "de", short=True)
        assert result == "15.03.2025"

    @pytest.mark.unit
    def test_format_date_none(self) -> None:
        assert format_date(None) == ""

    @pytest.mark.unit
    def test_format_datetime_none(self) -> None:
        assert format_datetime(None) == ""

    @pytest.mark.unit
    def test_format_number_english(self) -> None:
        result = format_number(1234567, "en")
        assert result == "1,234,567"

    @pytest.mark.unit
    def test_format_number_german(self) -> None:
        result = format_number(1234567, "de")
        assert result == "1.234.567"

    @pytest.mark.unit
    def test_format_number_float_english(self) -> None:
        result = format_number(1234.56, "en")
        assert result == "1,234.56"

    @pytest.mark.unit
    def test_format_number_float_german(self) -> None:
        result = format_number(1234.56, "de")
        assert result == "1.234,56"

    @pytest.mark.unit
    def test_format_datetime_chinese(self) -> None:
        dt = datetime(2025, 3, 15, 14, 30)
        result = format_datetime(dt, "zh")
        assert "2025" in result
        assert "03" in result
        assert "15" in result


# ---------------------------------------------------------------------------
# get_language_info()
# ---------------------------------------------------------------------------


class TestGetLanguageInfo:
    """Tests for get_language_info() utility."""

    @pytest.mark.unit
    def test_known_language(self) -> None:
        info = get_language_info("de")
        assert info is not None
        assert info["name"] == "German"
        assert info["native"] == "Deutsch"

    @pytest.mark.unit
    def test_unknown_language(self) -> None:
        assert get_language_info("xx") is None


# ---------------------------------------------------------------------------
# get_suggested_languages()
# ---------------------------------------------------------------------------


class TestGetSuggestedLanguages:
    """Tests for get_suggested_languages() utility."""

    @pytest.mark.unit
    def test_returns_at_most_six(self) -> None:
        """Result must contain at most 6 languages."""
        result = get_suggested_languages("en", "en,de;q=0.9,fr;q=0.8,es;q=0.7,it;q=0.6,pt;q=0.5,nl;q=0.4,zh;q=0.3")
        assert len(result) <= 6

    @pytest.mark.unit
    def test_current_locale_is_first(self) -> None:
        """Currently active language must always be the first entry."""
        result = get_suggested_languages("de", "")
        assert result[0]["code"] == "de"

    @pytest.mark.unit
    def test_includes_browser_preference(self) -> None:
        """Languages from Accept-Language header should be included."""
        result = get_suggested_languages("en", "fr;q=0.9,de;q=0.8")
        codes = [lang["code"] for lang in result]
        assert "fr" in codes
        assert "de" in codes

    @pytest.mark.unit
    def test_fallback_to_popular_languages(self) -> None:
        """Popular languages fill remaining slots when no browser prefs given."""
        result = get_suggested_languages("en", "")
        codes = [lang["code"] for lang in result]
        # en is current; popular fallbacks like zh, es, fr should be present
        assert "en" in codes
        # At least one other popular language should appear
        popular = {"zh", "es", "ar", "fr", "de", "ja", "pt", "hi", "ko"}
        assert popular & set(codes)

    @pytest.mark.unit
    def test_no_duplicates(self) -> None:
        """No language code should appear more than once."""
        result = get_suggested_languages("fr", "fr;q=1.0,de;q=0.9")
        codes = [lang["code"] for lang in result]
        assert len(codes) == len(set(codes))

    @pytest.mark.unit
    def test_all_entries_are_valid_languages(self) -> None:
        """Every returned entry must be a dict with required language fields."""
        result = get_suggested_languages("es", "ca;q=0.9")
        for entry in result:
            assert "code" in entry
            assert "name" in entry
            assert "native" in entry
            assert "flag" in entry

    @pytest.mark.unit
    def test_unknown_locale_falls_back_gracefully(self) -> None:
        """An unsupported current_locale must not crash and still return results."""
        result = get_suggested_languages("xx", "")
        assert len(result) > 0  # popular fallbacks still returned


# ---------------------------------------------------------------------------
# SUPPORTED_LANGUAGES metadata
# ---------------------------------------------------------------------------


class TestSupportedLanguages:
    """Tests for language metadata constants."""

    @pytest.mark.unit
    def test_ten_languages_supported(self) -> None:
        assert len(SUPPORTED_LANGUAGES) == 77

    @pytest.mark.unit
    def test_supported_codes_set(self) -> None:
        expected = {
            "en",
            "de",
            "fr",
            "es",
            "it",
            "pt",
            "nl",
            "nb",
            "no",
            "da",
            "sv",
            "fi",
            "is",
            "ga",
            "lb",
            "ca",
            "cy",
            "fy",
            "gl",
            "li",
            "vls",
            "nds",
            "pl",
            "cs",
            "sk",
            "hu",
            "sl",
            "hr",
            "ro",
            "bg",
            "el",
            "et",
            "lv",
            "lt",
            "sr",
            "tr",
            "uk",
            "he",
            "ar",
            "fa",
            "af",
            "zh",
            "zh-TW",
            "ja",
            "ko",
            "vi",
            "pa",
            "kn",
            "hi",
            "bn",
            "gu",
            "ml",
            "mr",
            "ta",
            "te",
            "ur",
            "si",
            "ne",
            "th",
            "km",
            "id",
            "ms",
            "jv",
            "tl",
            "mn",
            "kk",
            "uz",
            "az",
            "hy",
            "ka",
            "sw",
            "am",
            "ha",
            "yo",
            "ig",
            "zu",
            "eo",
        }
        assert SUPPORTED_LANGUAGE_CODES == expected

    @pytest.mark.unit
    def test_default_language_is_english(self) -> None:
        assert DEFAULT_LANGUAGE == "en"


# ---------------------------------------------------------------------------
# API endpoint tests
# ---------------------------------------------------------------------------


class TestI18nAPI:
    """Tests for the i18n API endpoints."""

    @pytest.mark.integration
    def test_list_languages(self, client: TestClient) -> None:
        """GET /api/i18n/languages should return all supported languages."""
        response = client.get("/api/i18n/languages")
        assert response.status_code == 200
        data = response.json()
        assert "languages" in data
        assert len(data["languages"]) == 77
        assert data["default"] == "en"
        # Verify each language has required fields
        for lang in data["languages"]:
            assert "code" in lang
            assert "name" in lang
            assert "native" in lang
            assert "flag" in lang

    @pytest.mark.integration
    def test_set_language(self, client: TestClient) -> None:
        """POST /api/i18n/language should set language preference."""
        response = client.post(
            "/api/i18n/language",
            json={"language": "de"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["language"] == "de"
        # Verify cookie was set
        assert "docuelevate_lang" in response.cookies

    @pytest.mark.integration
    def test_set_language_invalid_falls_back_to_default(self, client: TestClient) -> None:
        """Invalid language code should fall back to default."""
        response = client.post(
            "/api/i18n/language",
            json={"language": "invalid"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["language"] == "en"

    @pytest.mark.integration
    def test_set_language_persists_in_cookie(self, client: TestClient) -> None:
        """Language setting should be persisted in a cookie."""
        client.post("/api/i18n/language", json={"language": "fr"})
        # Subsequent requests should detect the language from cookie
        response = client.get("/api/i18n/languages")
        data = response.json()
        assert data["current"] == "fr"

    @pytest.mark.integration
    def test_base_html_uses_current_locale(self, client: TestClient) -> None:
        """The base template should set lang attribute to current locale."""
        # Set language to German
        client.post("/api/i18n/language", json={"language": "de"})
        # Load homepage
        response = client.get("/", follow_redirects=True)
        assert response.status_code == 200
        # The lang attribute should reflect the locale
        assert 'lang="de"' in response.text or 'lang="en"' in response.text

    @pytest.mark.integration
    def test_language_selector_in_nav(self, client: TestClient) -> None:
        """The navigation should contain the language selector with flag and search."""
        response = client.get("/", follow_redirects=True)
        if response.status_code == 200:
            # The selector renders a flag emoji (not the old fa-globe icon) and the
            # setLanguage JS helper for switching languages.
            assert "setLanguage" in response.text
            # The search input for filtering all languages must be present.
            assert "langSearch" in response.text
