"""Translation engine — loads JSON locale files and looks up strings by key.

Usage:
    from src.i18n import _, setup_i18n, get_language

    setup_i18n("ru")          # switch to Russian
    print(_("tray.settings"))  # → "Настройки"
"""

from __future__ import annotations

import functools
import json
import logging
import sys
from pathlib import Path

log = logging.getLogger(__name__)

# When frozen by PyInstaller, data files are under sys._MEIPASS.
# In development they live next to this file at src/locales/.
if getattr(sys, "frozen", False):
    _LOCALES_DIR = Path(sys._MEIPASS) / "src" / "locales"  # type: ignore[attr-defined]
else:
    _LOCALES_DIR = Path(__file__).parent.parent / "locales"

# Languages available in the UI (code → display name)
SUPPORTED_LANGUAGES: dict[str, str] = {
    "en": "English",
    "ru": "Русский",
    "zh_CN": "中文（简体）",
}

_FALLBACK_LANG = "en"

# Module-level singleton storage — shared across importlib.reload() because
# all Translator versions' metaclass methods look up this name through the
# same module __dict__ (which reload() updates in-place).
_translator_instance: Translator | None = None  # type: ignore[name-defined]


class _TranslatorMeta(type):
    """Metaclass that mirrors *_instance* assignments to the module-level store.

    When test fixtures set ``Translator._instance = None`` (even on a
    class object from before an importlib.reload()), the assignment is
    intercepted here and forwarded to the module-level ``_translator_instance``
    so that the singleton state remains consistent across class objects.
    """

    def __setattr__(cls, name: str, value: object) -> None:
        if name == "_instance":
            # Keep the module-level store in sync.
            import src.i18n.translator as _self_mod

            _self_mod._translator_instance = value  # type: ignore[assignment]
        super().__setattr__(name, value)


class Translator(metaclass=_TranslatorMeta):
    """Singleton translator — holds current locale strings + English fallback."""

    _instance: Translator | None = None

    def __init__(self, lang: str = "en") -> None:
        self._lang = lang
        self._strings: dict[str, str] = {}
        self._fallback: dict[str, str] = {}
        self._load(lang)

    # ------------------------------------------------------------------ #
    # Singleton access
    # ------------------------------------------------------------------ #

    @classmethod
    def get_instance(cls) -> Translator:
        global _translator_instance
        if _translator_instance is None:
            _translator_instance = cls(_FALLBACK_LANG)
            cls._instance = _translator_instance
        return _translator_instance

    @classmethod
    def setup(cls, lang: str) -> None:
        """Replace the singleton with a new instance for *lang*."""
        global _translator_instance
        _translator_instance = cls(lang)
        cls._instance = _translator_instance

    # ------------------------------------------------------------------ #
    # Loading
    # ------------------------------------------------------------------ #

    def _load(self, lang: str) -> None:
        # Always load English as fallback first
        fallback_path = _LOCALES_DIR / "en.json"
        if fallback_path.exists():
            try:
                with open(fallback_path, encoding="utf-8") as f:
                    self._fallback = json.load(f)
            except Exception as exc:
                log.warning("Failed to load fallback locale (en.json): %s", exc)
                self._fallback = {}
        else:
            log.warning("Fallback locale not found: %s", fallback_path)
            self._fallback = {}

        if lang == _FALLBACK_LANG:
            self._strings = self._fallback
        else:
            path = _LOCALES_DIR / f"{lang}.json"
            if path.exists():
                try:
                    with open(path, encoding="utf-8") as f:
                        self._strings = json.load(f)
                    log.debug("Locale loaded: %s (%d keys)", lang, len(self._strings))
                except Exception as exc:
                    log.warning("Failed to load locale %s: %s — falling back to English", lang, exc)
                    self._strings = self._fallback
            else:
                log.warning("Locale file not found: %s — falling back to English", path)
                self._strings = self._fallback

        self._lang = lang

    # ------------------------------------------------------------------ #
    # Translation
    # ------------------------------------------------------------------ #

    def translate(self, key: str, **kwargs: object) -> str:
        """Return the translated string for *key*, with optional {placeholder} substitution."""
        text = self._strings.get(key) or self._fallback.get(key, key)
        if kwargs:
            try:
                text = text.format(**kwargs)
            except (KeyError, ValueError):
                pass
        return text

    @property
    def language(self) -> str:
        return self._lang


# ------------------------------------------------------------------ #
# Module-level helpers
# ------------------------------------------------------------------ #


@functools.lru_cache(maxsize=1024)
def _cached_translate(key: str, lang: str) -> str:
    """Cached translation lookup for static keys (no kwargs).

    The *lang* parameter is included in the cache key so that switching
    languages automatically uses separate cache entries.  Call
    ``clear_translation_cache()`` to free memory when the language changes.
    """
    return Translator.get_instance().translate(key)


def clear_translation_cache() -> None:
    """Invalidate all cached translation lookups.

    Clears the module-level ``_cached_translate`` lru_cache.  When the
    translator module has been reloaded (e.g. during tests that exercise the
    PyInstaller frozen-path branch), there may be a second reference to an
    older ``_cached_translate`` function object stored in ``src.i18n``'s
    namespace.  Both are cleared here to guarantee a clean slate.
    """
    _cached_translate.cache_clear()
    # Clear any older _cached_translate that may live in src.i18n after a
    # module reload — its reference differs from the current _cached_translate.
    _init_mod = sys.modules.get("src.i18n")
    if _init_mod is not None:
        _older_fn = getattr(_init_mod, "_cached_translate", None)
        if _older_fn is not None and _older_fn is not _cached_translate:
            _older_fn.cache_clear()


def setup_i18n(lang: str) -> None:
    """Initialise (or re-initialise) the global translator for *lang*."""
    if lang not in SUPPORTED_LANGUAGES:
        log.warning("Unsupported language %r — falling back to 'en'", lang)
        lang = _FALLBACK_LANG
    Translator.setup(lang)
    clear_translation_cache()
    log.debug("i18n: switched to '%s'", lang)


def get_language() -> str:
    """Return the currently active language code."""
    return Translator.get_instance().language
