"""
AliothPress i18n — lightweight JSON-based translation system.

Admin translations:
  Usage in templates:  {{ t('sidebar.dashboard') }}
  Usage in Python:     from i18n import t, t_for, set_admin_language
  Translations stored in static/i18n/{lang_code}.json

  t()      — translates using current session language (per-request)
  t_for()  — translates for a specific language (for emails, background tasks)

Public translations:
  Usage in templates:  {{ pt('read_more') }}
  Translations stored in static/i18n/public.json (all languages in one file)
  Language determined by visitor's current_language, NOT admin language.

  pt()     — translates using visitor's language from g.current_language
  pt_for() — translates for a specific language (e.g. unsubscribe page)
"""

import json
import os

from flask import g

# In-memory cache: { 'en': { 'sidebar.dashboard': 'Dashboard', ... }, 'de': { ... } }
_translations = {}

# Public translations cache: { 'read_more': { 'en': 'Read more', 'de': 'Weiterlesen', ... }, ... }
_public_translations = {}

# Current admin language (per-request, set via before_request)
_current_lang = 'en'
_fallback_lang = 'en'

# Path to translation files
_i18n_dir = None


def init_i18n(app):
    """Initialize i18n system. Call once at app startup."""
    global _i18n_dir
    _i18n_dir = os.path.join(app.static_folder, 'i18n')

    # Pre-load English (fallback) at startup
    _load_language('en')

    # Pre-load public translations (all languages in one file)
    _load_public_translations()

    # Make t(), pt(), t_for(), pt_for(), and get_admin_language() available in all Jinja2 templates
    app.jinja_env.globals['t'] = t
    app.jinja_env.globals['t_for'] = t_for
    app.jinja_env.globals['pt'] = pt
    app.jinja_env.globals['pt_for'] = pt_for
    app.jinja_env.globals['get_admin_language'] = get_admin_language

    # Register date formatting filters (localized month names + flexible format)
    app.jinja_env.filters['format_date'] = format_date
    app.jinja_env.filters['format_datetime'] = format_datetime


def _load_language(lang_code):
    """Load a language JSON file into memory cache."""
    global _translations
    if lang_code in _translations:
        return  # Already loaded

    filepath = os.path.join(_i18n_dir, f'{lang_code}.json')
    if not os.path.exists(filepath):
        return

    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)

        # Flatten nested keys: { "sidebar": { "dashboard": "Dashboard" } }
        # becomes: { "sidebar.dashboard": "Dashboard" }
        flat = {}
        for section_key, section_val in data.items():
            if section_key.startswith('_'):
                continue  # Skip _meta, _comment, etc.
            if isinstance(section_val, dict):
                for key, value in section_val.items():
                    flat[f'{section_key}.{key}'] = value
            elif isinstance(section_val, str):
                flat[section_key] = section_val

        _translations[lang_code] = flat
    except (json.JSONDecodeError, IOError):
        pass  # Silently skip broken files


def set_admin_language(lang_code):
    """Set the current admin language for this request."""
    global _current_lang
    _current_lang = lang_code
    _load_language(lang_code)


def get_admin_language():
    """Get current admin language code."""
    return _current_lang


def t(key, **kwargs):
    """Translate a key. Supports {variable} substitution.

    Examples:
        t('sidebar.dashboard')           → "Dashboard"
        t('flash.invite_sent', email=x)  → "Invitation sent to user@example.com!"

    Falls back to English if key not found in current language.
    Falls back to the key itself if not found anywhere.
    """
    # Try current language
    lang_data = _translations.get(_current_lang, {})
    value = lang_data.get(key)

    # Fallback to English
    if not value and _current_lang != _fallback_lang:
        fallback_data = _translations.get(_fallback_lang, {})
        value = fallback_data.get(key)

    # Final fallback: return the key itself (makes missing translations visible)
    if not value:
        return key

    # Substitute {variables} if any kwargs provided
    if kwargs:
        try:
            value = value.format(**kwargs)
        except (KeyError, IndexError):
            pass  # Return unformatted if substitution fails

    return value


def t_for(key, lang_code, **kwargs):
    """Translate a key for a specific language (not tied to current session).

    Use this when the recipient's language differs from the logged-in admin's:
      - Password reset emails  → user.admin_language
      - Invite emails          → current_user.admin_language (inviter's language)
      - Any out-of-request context

    Examples:
        t_for('email.reset_title', user.admin_language)
        t_for('email.invite_subject', 'de', site_name='My Site')

    Falls back to English, then to the key itself.
    """
    _load_language(lang_code)

    # Try requested language
    lang_data = _translations.get(lang_code, {})
    value = lang_data.get(key)

    # Fallback to English
    if not value and lang_code != _fallback_lang:
        fallback_data = _translations.get(_fallback_lang, {})
        value = fallback_data.get(key)

    # Final fallback: return the key itself
    if not value:
        return key

    # Substitute {variables}
    if kwargs:
        try:
            value = value.format(**kwargs)
        except (KeyError, IndexError):
            pass

    return value


def _load_public_translations():
    """Load public.json into memory cache at startup.

    public.json structure:
      { "read_more": { "en": "Read more", "de": "Weiterlesen", ... }, ... }
    """
    global _public_translations
    filepath = os.path.join(_i18n_dir, 'public.json')
    if not os.path.exists(filepath):
        return

    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            _public_translations = json.load(f)
    except (json.JSONDecodeError, IOError):
        pass  # Silently skip if broken


def pt(key, **kwargs):
    """Public translate — uses visitor's language, NOT admin language.

    Reads from static/i18n/public.json (cached in memory).
    Language is taken from g.current_language (set in app.py context_processor).
    Falls back to English, then to the key itself.
    Supports {variable} substitution.

    Usage in templates:  {{ pt('read_more') }}
    Usage in Python:     pt('unsubscribed_body', site_name='My Site')
    """
    translations = _public_translations.get(key)
    if not translations:
        return key

    lang = getattr(g, 'current_language', None) or _fallback_lang
    value = translations.get(lang)

    # Fallback to English
    if not value and lang != _fallback_lang:
        value = translations.get(_fallback_lang)

    if not value:
        return key

    # Substitute {variables}
    if kwargs:
        try:
            value = value.format(**kwargs)
        except (KeyError, IndexError):
            pass

    return value


def pt_for(key, lang_code, **kwargs):
    """Public translate for a specific language (not tied to g.current_language).

    Use this when rendering public content outside a normal request context,
    e.g. the unsubscribe page where the language comes from a URL parameter.

    Usage:  pt_for('unsubscribed_body', 'de', site_name='My Site')
    """
    translations = _public_translations.get(key)
    if not translations:
        return key

    value = translations.get(lang_code)

    # Fallback to English
    if not value and lang_code != _fallback_lang:
        value = translations.get(_fallback_lang)

    if not value:
        return key

    if kwargs:
        try:
            value = value.format(**kwargs)
        except (KeyError, IndexError):
            pass

    return value


# ============================================================================
# DATE FORMATTING (localized)
# ============================================================================

# Fallback English month names (used if translation file has no dates section)
_EN_MONTHS_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
_EN_MONTHS_LONG = ['January', 'February', 'March', 'April', 'May', 'June',
                   'July', 'August', 'September', 'October', 'November', 'December']


def _get_date_translations(lang=None):
    """Get month names and format patterns for a language.

    Returns (months_short, months_long, fmt_date, fmt_datetime).
    Falls back to English, then to hardcoded defaults.
    """
    lang = lang or _current_lang

    # Helper: try lang, then fallback
    def _get(key):
        val = _translations.get(lang, {}).get(key)
        if val is None and lang != _fallback_lang:
            val = _translations.get(_fallback_lang, {}).get(key)
        return val

    months_short = _get('dates.months_short') or _EN_MONTHS_SHORT
    months_long = _get('dates.months_long') or _EN_MONTHS_LONG
    fmt_date = _get('dates.format_date') or '{month_short} {day}, {year}'
    fmt_datetime = _get('dates.format_datetime') or '{month_short} {day}, {time}'

    return months_short, months_long, fmt_date, fmt_datetime


def format_date(dt, fmt=None):
    """Jinja filter: format a date using current admin language.

    Usage in templates:
        {{ post.updated_at|format_date }}           → "Mar 12, 2026"  (en)
        {{ post.updated_at|format_date('long') }}   → "March 12, 2026"

    Each language JSON can customize:
        - dates.months_short / dates.months_long  (month names)
        - dates.format_date  (order and separators)

    Safe: returns '' for None, never raises.
    """
    if dt is None:
        return ''

    months_short, months_long, fmt_pattern, _ = _get_date_translations()

    try:
        month_idx = dt.month - 1  # 0-based
        result = fmt_pattern.format(
            day=dt.day,
            day0=str(dt.day).zfill(2),
            month=dt.month,
            month0=str(dt.month).zfill(2),
            month_short=months_short[month_idx],
            month_long=months_long[month_idx],
            year=dt.year
        )
        return result
    except (AttributeError, IndexError, KeyError):
        # Fallback: ISO format
        try:
            return dt.strftime('%Y-%m-%d')
        except Exception:
            return str(dt)


def format_datetime(dt, fmt=None):
    """Jinja filter: format a datetime using current admin language.

    Usage in templates:
        {{ log.created_at|format_datetime }}         → "Mar 12, 14:30"  (en)

    Each language JSON can customize:
        - dates.months_short  (month names)
        - dates.format_datetime  (order and separators)

    Safe: returns '' for None, never raises.
    """
    if dt is None:
        return ''

    months_short, months_long, _, fmt_pattern = _get_date_translations()

    try:
        month_idx = dt.month - 1
        time_str = dt.strftime('%H:%M')
        result = fmt_pattern.format(
            day=dt.day,
            day0=str(dt.day).zfill(2),
            month=dt.month,
            month0=str(dt.month).zfill(2),
            month_short=months_short[month_idx],
            month_long=months_long[month_idx],
            year=dt.year,
            time=time_str
        )
        return result
    except (AttributeError, IndexError, KeyError):
        try:
            return dt.strftime('%Y-%m-%d %H:%M')
        except Exception:
            return str(dt)
