"""Internationalization (i18n) support for WebDrop Bridge.

Provides a simple JSON-based translation system.  Translation files are stored
in ``resources/translations/`` (e.g. ``en.json``, ``de.json``, ``fr.json``).

Usage::

    from webdrop_bridge.utils.i18n import tr

    # Simple lookup
    self.setWindowTitle(tr("settings.title"))

    # With named format arguments
    label.setText(tr("status.opened", name="file.pdf"))

To add a new language, place a JSON file named ``<code>.json`` in
``resources/translations/`` and optionally add an entry to
:attr:`Translator.BUILTIN_LANGUAGES` for a nicer display name.
"""

import json
import logging
import sys
from pathlib import Path
from typing import Dict, Optional

logger = logging.getLogger(__name__)


class Translator:
    """Manages translations for the application.

    Loads translations from UTF-8 JSON files that use dot-notation string keys.
    Falls back to the English translation (and ultimately to the bare key) when
    a translation is missing.

    Attributes:
        BUILTIN_LANGUAGES: Mapping of language code → display name for languages
            that ship with the application.  Add entries here when including new
            translation files.
    """

    #: Human-readable display names for supported language codes.
    #: Unknown codes fall back to their uppercase code string.
    BUILTIN_LANGUAGES: Dict[str, str] = {
        "en": "English",
        "de": "Deutsch",
        "fr": "Français",
        "it": "Italiano",
        "ru": "Русский",
        "zh": "中文",
    }

    # Backward-compatible key aliases for renamed translation keys.
    # This allows newer UI code to reuse existing localized strings.
    KEY_ALIASES: Dict[str, str] = {
        "settings.web_source.url_label": "settings.web_url.label",
        "settings.web_source.open_btn": "settings.web_url.open_btn",
        "settings.web_source.url_mappings_label": "settings.url_mappings.label",
        "settings.web_source.col_url_prefix": "settings.url_mappings.col_prefix",
        "settings.web_source.col_local_path": "settings.url_mappings.col_path",
        "settings.web_source.add_mapping_btn": "settings.url_mappings.add_btn",
        "settings.web_source.edit_mapping_btn": "settings.url_mappings.edit_btn",
        "settings.web_source.remove_mapping_btn": "settings.url_mappings.remove_btn",
        "settings.web_source.add_mapping_title": "settings.add_mapping.url_title",
        "settings.web_source.add_mapping_url_prompt": "settings.add_mapping.url_prompt",
        "settings.web_source.add_mapping_path_prompt": "settings.add_mapping.path_prompt",
        "settings.web_source.edit_mapping_title": "settings.edit_mapping.title",
        "settings.web_source.edit_mapping_url_prompt": "settings.edit_mapping.url_prompt",
        "settings.web_source.edit_mapping_path_prompt": "settings.edit_mapping.path_prompt",
        "settings.web_source.select_mapping_to_edit": "settings.error.select_mapping",
        "settings.logging.level_label": "settings.log_level.label",
        "settings.logging.file_label": "settings.log_file.label",
        "settings.logging.browse_btn": "settings.log_file.browse_btn",
        "settings.logging.select_file_title": "settings.select_log_file.title",
        "settings.paths.select_dir_title": "settings.select_directory.title",
        "settings.urls.add_title": "settings.add_url.title",
        "settings.urls.add_prompt": "settings.add_url.prompt",
        "settings.profiles.save_title": "settings.profile.save.title",
        "settings.profiles.save_prompt": "settings.profile.save.prompt",
        "settings.profiles.select_to_load": "settings.error.select_profile_load",
        "settings.profiles.select_to_delete": "settings.error.select_profile_delete",
        "settings.profiles.export_title": "settings.export_config.title",
        "settings.profiles.import_title": "settings.import_config.title",
    }

    def __init__(self) -> None:
        self._language: str = "en"
        self._translations: Dict[str, str] = {}
        self._fallback: Dict[str, str] = {}
        self._translations_dir: Optional[Path] = None

    def initialize(self, language: str, translations_dir: Path) -> None:
        """Initialize the translator with a language and translations directory.

        Args:
            language: Language code (e.g. ``"en"``, ``"de"``, ``"fr"``) or
                ``"auto"`` to detect from the system locale.
            translations_dir: Directory containing the ``.json`` translation files.
        """
        self._translations_dir = translations_dir

        # Resolve "auto" to system locale
        if language == "auto":
            language = self._detect_system_language()
            logger.debug(f"Auto-detected language: {language}")

        # Load English as fallback first
        en_path = translations_dir / "en.json"
        if en_path.exists():
            self._fallback = self._load_file(en_path)
            logger.debug(f"Loaded English fallback translations ({len(self._fallback)} keys)")
        else:
            logger.warning(f"English translation file not found at {en_path}")

        # Load requested language
        self._language = language
        if language != "en":
            lang_path = translations_dir / f"{language}.json"
            if lang_path.exists():
                self._translations = self._load_file(lang_path)
                logger.debug(f"Loaded '{language}' translations ({len(self._translations)} keys)")
            else:
                logger.warning(
                    f"Translation file not found for language '{language}', "
                    "falling back to English"
                )
                self._translations = {}
        else:
            self._translations = self._fallback

    def tr(self, key: str, **kwargs: str) -> str:
        """Get translated string for the given key.

        Args:
            key: Translation key using dot-notation (e.g. ``"toolbar.home"``).
            **kwargs: Named format arguments applied to the translated string.

        Returns:
            Translated and formatted string.  Returns the *key* itself when no
            translation is found, so missing keys are always visible.
        """
        text = self._translations.get(key) or self._fallback.get(key)

        if text is None:
            alias_key = self.KEY_ALIASES.get(key)
            if alias_key:
                text = self._translations.get(alias_key) or self._fallback.get(alias_key)

        if text is None:
            text = key

        if kwargs:
            try:
                text = text.format(**kwargs)
            except (KeyError, ValueError) as e:
                logger.debug(f"Translation format error for key '{key}': {e}")
        return text

    def get_current_language(self) -> str:
        """Get the currently active language code (e.g. ``"de"``)."""
        return self._language

    def get_available_languages(self) -> Dict[str, str]:
        """Return available languages as ``{code: display_name}``.

        Discovers language files at runtime so newly added JSON files are
        automatically included without code changes.

        Returns:
            Ordered dict mapping language code → human-readable display name.
        """
        if self._translations_dir is None:
            return {"en": "English"}

        languages: Dict[str, str] = {}
        for lang_file in sorted(self._translations_dir.glob("*.json")):
            code = lang_file.stem
            name = self.BUILTIN_LANGUAGES.get(code, code.upper())
            languages[code] = name
        return languages

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _load_file(self, path: Path) -> Dict[str, str]:
        """Load a JSON translation file.

        Args:
            path: Path to the UTF-8 encoded JSON translation file.

        Returns:
            Dictionary of translation keys to translated strings, or an empty
            dict when the file cannot be read or parsed.
        """
        try:
            with open(path, "r", encoding="utf-8") as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.error(f"Failed to load translation file {path}: {e}")
            return {}

    def _detect_system_language(self) -> str:
        """Detect system language from locale or platform settings.

        On Windows, attempts to read the UI language via the WinAPI before
        falling back to the ``locale`` module.

        Returns:
            Best-matching supported language code, or ``"en"`` as fallback.
        """
        import locale

        try:
            lang_code: Optional[str] = None

            if sys.platform.startswith("win"):
                # Windows: use GetUserDefaultUILanguage for accuracy
                try:
                    import ctypes

                    lcid = ctypes.windll.kernel32.GetUserDefaultUILanguage()  # type: ignore[attr-defined]
                    # Subset of LCID → ISO 639-1 mappings
                    lcid_map: Dict[int, str] = {
                        0x0407: "de",  # German (Germany)
                        0x0C07: "de",  # German (Austria)
                        0x0807: "de",  # German (Switzerland)
                        0x040C: "fr",  # French (France)
                        0x080C: "fr",  # French (Belgium)
                        0x0C0C: "fr",  # French (Canada)
                        0x100C: "fr",  # French (Switzerland)
                        0x0410: "it",  # Italian (Italy)
                        0x0810: "it",  # Italian (Switzerland)
                        0x0419: "ru",  # Russian
                        0x0804: "zh",  # Chinese Simplified
                        0x0404: "zh",  # Chinese Traditional
                        0x0409: "en",  # English (US)
                        0x0809: "en",  # English (UK)
                    }
                    lang_code = lcid_map.get(lcid)
                except Exception:
                    pass

            if not lang_code:
                raw = locale.getdefaultlocale()[0] or ""
                lang_code = raw.split("_")[0].lower() if raw else None

            if lang_code and lang_code in self.BUILTIN_LANGUAGES:
                return lang_code

        except Exception as e:
            logger.debug(f"Language auto-detection failed: {e}")

        return "en"


# ---------------------------------------------------------------------------
# Module-level singleton and public API
# ---------------------------------------------------------------------------

_translator = Translator()


def _ensure_initialized() -> None:
    """Initialize translator lazily with default settings if needed."""
    if _translator._translations_dir is not None:  # type: ignore[attr-defined]
        return
    _translator.initialize("en", get_translations_dir())


def initialize(language: str, translations_dir: Path) -> None:
    """Initialize the global translator.

    Should be called **once at application startup**, before any UI is shown.

    Args:
        language: Language code (e.g. ``"de"``) or ``"auto"`` for system
            locale detection.
        translations_dir: Directory containing the ``.json`` translation files.
    """
    _translator.initialize(language, translations_dir)


def tr(key: str, **kwargs: str) -> str:
    """Translate a string by key.

    Args:
        key: Translation key (e.g. ``"toolbar.home"``).
        **kwargs: Named format arguments (e.g. ``name="file.pdf"``).

    Returns:
        Translated string with any format substitutions applied.
    """
    _ensure_initialized()
    text = _translator.tr(key, **kwargs)

    # If lookup failed and translator points to a non-default directory (e.g. tests
    # overriding translator state), retry from default bundled translations.
    if text == key:
        default_dir = get_translations_dir()
        current_dir = _translator._translations_dir  # type: ignore[attr-defined]
        if current_dir != default_dir:
            _translator.initialize("en", default_dir)
            text = _translator.tr(key, **kwargs)

    return text


def get_current_language() -> str:
    """Return the currently active language code (e.g. ``"de"``)."""
    return _translator.get_current_language()


def get_available_languages() -> Dict[str, str]:
    """Return all available languages as ``{code: display_name}``."""
    _ensure_initialized()
    return _translator.get_available_languages()


def get_translations_dir() -> Path:
    """Resolve the translations directory for the current runtime context.

    Handles development mode, PyInstaller bundles, and MSI installations
    by searching the known candidate paths in order.

    Returns:
        Path to the ``resources/translations`` directory.
    """
    if hasattr(sys, "_MEIPASS"):
        # PyInstaller bundle
        return Path(sys._MEIPASS) / "resources" / "translations"  # type: ignore[attr-defined]
    # Development mode or installed Python package
    return Path(__file__).parent.parent.parent.parent / "resources" / "translations"
