"""
Context processors pour le CMS.

Fournit des fonctions helper et des menus pre-charges pour les templates.
"""
from django.core.cache import cache
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver

from traduction.helpers import prefetch_translations
from traduction.models import Language

from .models import Setting, Menu, MenuItem


# =============================================================================
# Fonctions Helper
# =============================================================================

def get_setting(slug, default=None, language_code=None, formatted=False):
    """
    Recupere la valeur d'un parametre par son slug.
    Utilise le cache pour les performances.
    Prefetch les traductions pour la langue courante.

    Args:
        slug: Le slug unique du parametre
        default: Valeur par defaut si non trouve
        language_code: Code de la langue (optionnel)
        formatted: Si True, retourne la valeur formatee HTML

    Returns:
        La valeur brute ou formatee, ou default

    Usage dans les templates:
        {{ get_setting.site_email }}
        ou avec le tag:
        {% get_setting 'site_email' as email %}
    """
    # Determiner la langue courante
    if language_code is None:
        default_lang = Language.get_default()
        language_code = default_lang.code if default_lang else 'fr'

    cache_suffix = '_formatted' if formatted else '_raw'
    cache_key = f'cms_setting_{slug}_{language_code}{cache_suffix}'
    value = cache.get(cache_key)

    if value is None:
        try:
            setting = Setting.objects.select_related('type').get(slug=slug, is_active=True)
            # Prefetch les traductions
            prefetch_translations([setting], language_code=language_code)

            # Essayer d'obtenir la traduction de la valeur
            translated_value = setting.get_translated('value')
            if translated_value:
                raw_value = translated_value
            else:
                raw_value = setting.get_raw_value()

            if formatted:
                value = setting.get_value()
            else:
                value = raw_value

            cache.set(cache_key, value, 3600)  # Cache 1 heure
        except Setting.DoesNotExist:
            value = default

    return value


def get_menu(slug, language_code=None):
    """
    Recupere un menu avec tous ses elements traduits.

    Args:
        slug: Le slug unique du menu
        language_code: Code de la langue (optionnel)

    Returns:
        Instance Menu avec items traduits ou None

    Usage dans les templates:
        {% get_menu 'header' as nav %}
        {% for item in nav.get_root_items %}...{% endfor %}
    """
    # Determiner la langue courante
    if language_code is None:
        default_lang = Language.get_default()
        language_code = default_lang.code if default_lang else 'fr'

    try:
        # Charger le menu avec tous ses items
        menu = Menu.objects.get(slug=slug, active=True)

        # Charger tous les items actifs avec leurs relations
        all_items = list(MenuItem.objects.filter(
            menu=menu,
            active=True
        ).select_related('icon', 'icon__group', 'page', 'parent').order_by('parent__order', 'parent__id', 'order'))

        # Appliquer les traductions a tous les items
        if all_items:
            prefetch_translations(all_items, language_code=language_code)

        # Organiser les items en hierarchie
        items_by_parent = {}
        root_items = []

        for item in all_items:
            if item.parent_id is None:
                root_items.append(item)
            else:
                if item.parent_id not in items_by_parent:
                    items_by_parent[item.parent_id] = []
                items_by_parent[item.parent_id].append(item)

        # Attacher les enfants traduits a chaque item
        for item in all_items:
            item._translated_children = items_by_parent.get(item.id, [])

        # Attacher les items racine traduits au menu
        menu._translated_root_items = root_items

        return menu

    except Menu.DoesNotExist:
        return None


class SettingProxy:
    """
    Proxy pour acceder aux settings via la syntaxe point.

    Permet d'utiliser {{ settings.site_name }}, {{ settings.email }}, etc.

    Mapping des attributs vers les slugs:
        site_name -> site-name
        tagline -> site-tagline
        phone -> site-phone
        email -> site-email
        address -> site-address
        facebook_url -> social-facebook
        twitter_url -> social-twitter
        linkedin_url -> social-linkedin
        youtube_url -> social-youtube
        meta_description -> seo-meta-description
        meta_keywords -> seo-meta-keywords
    """

    # Mapping attribut -> slug (pour compatibilite avec SiteConfiguration)
    SLUG_MAPPING = {
        'site_name': 'site-name',
        'tagline': 'site-tagline',
        'phone': 'site-phone',
        'email': 'site-email',
        'address': 'site-address',
        'facebook_url': 'social-facebook',
        'twitter_url': 'social-twitter',
        'linkedin_url': 'social-linkedin',
        'youtube_url': 'social-youtube',
        'instagram_url': 'social-instagram',
        'meta_description': 'seo-meta-description',
        'meta_keywords': 'seo-meta-keywords',
    }

    def __init__(self, language_code=None):
        self._language_code = language_code

    def __getattr__(self, name):
        if name.startswith('_'):
            return super().__getattribute__(name)

        # Convertir le nom d'attribut en slug
        slug = self.SLUG_MAPPING.get(name, name.replace('_', '-'))

        return get_setting(slug, language_code=self._language_code) or ''


# =============================================================================
# Context Processor
# =============================================================================

def cms_context(request):
    """
    Context processor pour rendre les fonctions CMS disponibles dans les templates.

    Ajoute:
        - get_setting: fonction pour recuperer un parametre
        - get_menu: fonction pour recuperer un menu
        - settings: proxy pour acceder aux settings via settings.slug
        - header_menu: menu header pre-charge
        - footer_menu: menu footer pre-charge
    """
    # Recuperer le code de la langue courante depuis la request
    language_code = getattr(request, 'current_language_code', None)
    if not language_code:
        default_lang = Language.get_default()
        language_code = default_lang.code if default_lang else 'fr'

    # Wrapper pour passer la langue aux fonctions
    def get_setting_with_lang(slug, default=None):
        return get_setting(slug, default=default, language_code=language_code)

    def get_menu_with_lang(slug):
        return get_menu(slug, language_code=language_code)

    return {
        'get_setting': get_setting_with_lang,
        'get_menu': get_menu_with_lang,
        'settings': SettingProxy(language_code=language_code),
        'header_menu': get_menu('header', language_code=language_code),
        'footer_menu': get_menu('footer', language_code=language_code),
    }


# =============================================================================
# Signaux pour l'invalidation du cache
# =============================================================================

def _get_all_language_codes():
    """Recupere tous les codes de langue actifs."""
    try:
        return list(Language.objects.filter(is_active=True).values_list('code', flat=True))
    except Exception:
        return ['fr', 'en']


@receiver([post_save, post_delete], sender=Setting)
def invalidate_setting_cache(sender, instance, **kwargs):
    """Invalide le cache quand un Setting est modifie ou supprime."""
    # Invalider pour toutes les langues et les deux formats (raw et formatted)
    for lang_code in _get_all_language_codes():
        cache.delete(f'cms_setting_{instance.slug}_{lang_code}_raw')
        cache.delete(f'cms_setting_{instance.slug}_{lang_code}_formatted')


@receiver([post_save, post_delete], sender=Menu)
def invalidate_menu_cache(sender, instance, **kwargs):
    """Invalide le cache quand un Menu est modifie ou supprime."""
    cache.delete(f'cms_menu_{instance.slug}')


@receiver([post_save, post_delete], sender=MenuItem)
def invalidate_menu_item_cache(sender, instance, **kwargs):
    """Invalide le cache du menu parent quand un MenuItem est modifie."""
    if instance.menu_id:
        try:
            menu = Menu.objects.get(pk=instance.menu_id)
            cache.delete(f'cms_menu_{menu.slug}')
        except Menu.DoesNotExist:
            pass
