"""
Helpers pour recuperer les traductions dans les vues et templates

Utilisation dans les vues:
    from traduction.helpers import get_translated_value, TranslatableMixin, TranslatedViewMixin

    # Option 1: Fonction
    title = get_translated_value(article, 'title', request)

    # Option 2: Mixin dans le modele
    class Article(TranslatableMixin, models.Model):
        ...

    article.get_translated('title', 'en')

    # Option 3: Mixin dans la vue (RECOMMANDE - avec prefetch optimise)
    class ArticleListView(TranslatedViewMixin, ListView):
        model = Article
        translate_fields = ['title', 'content']

    # Option 4: Prefetch manuel
    articles = NewsArticle.objects.all()
    prefetch_translations(articles, language_code='fr')
    # Maintenant article.title retourne la traduction
"""

from django.contrib.contenttypes.models import ContentType

from .models import Language, Dictionnaire
from .registry import get_translatable_fields, is_translatable


# Cle pour stocker les traductions en cache sur les objets
TRANSLATIONS_CACHE_KEY = '_translations_cache'
TRANSLATIONS_PREFETCHED_KEY = '_translations_prefetched'


def prefetch_translations(objects, language_code=None, fields=None):
    """
    Prefetch toutes les traductions pour une liste d'objets en UNE SEULE requete.

    Les traductions sont stockees en cache sur chaque objet et seront utilisees
    automatiquement par get_translated_value() et les template tags.

    Args:
        objects: Liste ou queryset d'objets (doivent etre du meme type)
        language_code: Code de la langue (optionnel, utilise la langue par defaut)
        fields: Liste des champs a traduire (optionnel, auto-detect depuis registry)

    Returns:
        Liste des objets avec traductions en cache

    Exemple:
        articles = list(NewsArticle.objects.all())
        prefetch_translations(articles, 'fr')
        for article in articles:
            print(article.title)  # Affiche la traduction, pas de requete DB
    """
    if not objects:
        return objects

    # Convertir en liste si queryset
    objects = list(objects)

    # Determiner le code de langue
    if language_code is None:
        default_lang = Language.get_default()
        language_code = default_lang.code if default_lang else 'fr'

    # Determiner les champs a traduire
    first_obj = objects[0]
    if fields is None:
        fields = get_translatable_fields(first_obj.__class__)

    if not fields:
        return objects

    # Obtenir le ContentType
    content_type = ContentType.objects.get_for_model(first_obj)

    # Recuperer les IDs des objets
    object_ids = [obj.pk for obj in objects if obj.pk]

    if not object_ids:
        return objects

    # Charger TOUTES les traductions en une seule requete
    translations = Dictionnaire.objects.filter(
        content_type=content_type,
        object_id__in=object_ids,
        language__code=language_code,
        language__is_active=True
    ).select_related('language')

    # Aussi charger les traductions de la langue par defaut pour fallback
    default_lang = Language.get_default()
    if default_lang and default_lang.code != language_code:
        default_translations = Dictionnaire.objects.filter(
            content_type=content_type,
            object_id__in=object_ids,
            language=default_lang
        )
        # Indexer par (object_id, field)
        default_trans_map = {
            (t.object_id, t.field): t.translation
            for t in default_translations
        }
    else:
        default_trans_map = {}

    # Indexer les traductions par (object_id, field)
    trans_map = {
        (t.object_id, t.field): t.translation
        for t in translations
    }

    # Appliquer les traductions aux objets
    for obj in objects:
        if not obj.pk:
            continue

        # Initialiser le cache
        cache = {}

        for field in fields:
            if not hasattr(obj, field):
                continue

            # Chercher la traduction
            key = (obj.pk, field)
            if key in trans_map:
                cache[field] = trans_map[key]
            elif key in default_trans_map:
                cache[field] = default_trans_map[key]
            else:
                # Fallback: valeur originale
                cache[field] = getattr(obj, field, '')

        # Stocker le cache sur l'objet
        setattr(obj, TRANSLATIONS_CACHE_KEY, cache)
        setattr(obj, TRANSLATIONS_PREFETCHED_KEY, True)

        # Remplacer les valeurs des champs par les traductions
        for field, value in cache.items():
            # Sauvegarder l'original
            if not hasattr(obj, f'_original_{field}'):
                setattr(obj, f'_original_{field}', getattr(obj, field, ''))
            # Appliquer la traduction
            setattr(obj, field, value)

    return objects


def get_translated_value(obj, field, request=None, language_code=None, fallback=True):
    """
    Recupere la valeur traduite d'un champ.

    Si l'objet a ete prefetche via prefetch_translations(), utilise le cache.
    Sinon, fait une requete DB.

    Args:
        obj: Instance du modele
        field: Nom du champ
        request: HttpRequest (pour recuperer la langue courante)
        language_code: Code de la langue (prioritaire sur request)
        fallback: Si True, retourne la valeur originale si pas de traduction

    Returns:
        Valeur traduite ou valeur originale
    """
    # Verifier le cache de prefetch en premier
    if getattr(obj, TRANSLATIONS_PREFETCHED_KEY, False):
        cache = getattr(obj, TRANSLATIONS_CACHE_KEY, {})
        if field in cache:
            return cache[field]

    # Determiner le code de langue
    if language_code is None:
        if request and hasattr(request, 'current_language_code'):
            language_code = request.current_language_code
        else:
            default_lang = Language.get_default()
            language_code = default_lang.code if default_lang else 'fr'

    # Recuperer la traduction depuis la DB
    translation = Dictionnaire.get_translation(obj, field, language_code)

    if translation:
        return translation

    # Fallback: valeur originale ou langue par defaut
    if fallback:
        # Essayer la langue par defaut si differente
        default_lang = Language.get_default()
        if default_lang and default_lang.code != language_code:
            translation = Dictionnaire.get_translation(obj, field, default_lang.code)
            if translation:
                return translation

        # Retourner la valeur originale du champ
        return getattr(obj, field, '')

    return None


def get_all_translations(obj, language_code=None):
    """
    Recupere toutes les traductions d'un objet

    Args:
        obj: Instance du modele
        language_code: Code de la langue (optionnel)

    Returns:
        Dict {field: translation, ...}
    """
    translations = {}
    qs = Dictionnaire.get_translations_for_object(obj, language_code)

    for trans in qs:
        if trans.language.code not in translations:
            translations[trans.language.code] = {}
        translations[trans.language.code][trans.field] = trans.translation

    return translations


def translate_queryset(queryset, fields, request=None, language_code=None):
    """
    Traduit un queryset en ajoutant les traductions comme attributs

    Args:
        queryset: QuerySet Django
        fields: Liste des champs a traduire
        request: HttpRequest
        language_code: Code de la langue

    Returns:
        Liste d'objets avec attributs traduits
    """
    if language_code is None and 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'

    result = []
    for obj in queryset:
        for field in fields:
            translated = get_translated_value(obj, field, language_code=language_code)
            setattr(obj, f'{field}_translated', translated)
        result.append(obj)

    return result


from .mixins import TranslatableMixin  # noqa: F401 - re-export pour retrocompatibilite


class TranslatedValueDescriptor:
    """
    Descripteur pour acceder aux traductions comme attributs

    Permet d'utiliser:
        article.title_translated  # au lieu de article.get_translated('title')
    """

    def __init__(self, field_name):
        self.field_name = field_name

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self

        # Recuperer depuis le cache si disponible
        cache_key = f'_translated_{self.field_name}'
        if hasattr(obj, cache_key):
            return getattr(obj, cache_key)

        # Calculer et mettre en cache
        value = get_translated_value(obj, self.field_name)
        setattr(obj, cache_key, value)
        return value

    def __set__(self, obj, value):
        cache_key = f'_translated_{self.field_name}'
        setattr(obj, cache_key, value)


class TranslatedViewMixin:
    """
    Mixin pour les vues Django qui traduit automatiquement les objets.

    OPTIMISE: Utilise prefetch_translations() pour charger toutes les
    traductions en UNE SEULE requete SQL, evitant le probleme N+1.

    Utilisation:
        class ArticleListView(TranslatedViewMixin, ListView):
            model = NewsArticle
            translate_fields = ['title', 'excerpt', 'content']

        class ArticleDetailView(TranslatedViewMixin, DetailView):
            model = NewsArticle
            # Si translate_fields n'est pas defini, utilise le registry

    Dans le template, accedez directement aux champs:
        {{ article.title }}  <!-- Affiche la traduction -->
        {{ article._original_title }}  <!-- Affiche l'original -->
    """
    translate_fields = None  # Liste des champs a traduire, ou None pour auto

    def get_translate_fields(self):
        """Retourne les champs a traduire"""
        if self.translate_fields:
            return self.translate_fields
        # Auto-detection depuis le registry
        if hasattr(self, 'model') and self.model:
            return get_translatable_fields(self.model)
        return []

    def get_current_language_code(self):
        """Retourne le code de la langue courante"""
        if hasattr(self, 'request'):
            return getattr(self.request, 'current_language_code', None)
        return None

    def translate_object(self, obj):
        """
        Traduit un seul objet (pour DetailView).
        Utilise prefetch_translations pour optimiser.
        """
        if obj is None:
            return obj

        language_code = self.get_current_language_code()
        fields = self.get_translate_fields()

        # Utiliser prefetch_translations meme pour un seul objet
        # pour beneficier du cache et de la logique de fallback
        prefetch_translations([obj], language_code=language_code, fields=fields)

        return obj

    def translate_queryset(self, queryset):
        """
        Traduit tous les objets d'un queryset en UNE SEULE requete.
        C'est la methode optimisee principale.
        """
        language_code = self.get_current_language_code()
        fields = self.get_translate_fields()

        # Prefetch toutes les traductions en une seule requete
        return prefetch_translations(queryset, language_code=language_code, fields=fields)

    def get_object(self, queryset=None):
        """Override pour traduire l'objet dans DetailView"""
        obj = super().get_object(queryset)
        return self.translate_object(obj)

    def get_queryset(self):
        """Override pour traduire le queryset dans ListView"""
        queryset = super().get_queryset()
        return self.translate_queryset(queryset)

    def get_context_data(self, **kwargs):
        """Ajoute les infos de langue au contexte"""
        context = super().get_context_data(**kwargs)
        context['current_language_code'] = self.get_current_language_code()
        context['translate_fields'] = self.get_translate_fields()
        return context


def translate_object(obj, language_code=None, fields=None):
    """
    Fonction utilitaire pour traduire un objet

    Args:
        obj: L'objet a traduire
        language_code: Code de la langue (optionnel)
        fields: Liste des champs (optionnel, sinon auto-detect)

    Returns:
        L'objet avec les champs traduits
    """
    if obj is None:
        return obj

    if fields is None:
        fields = get_translatable_fields(obj.__class__)

    if language_code is None:
        default_lang = Language.get_default()
        language_code = default_lang.code if default_lang else 'fr'

    for field in fields:
        if hasattr(obj, field):
            translated_value = get_translated_value(
                obj, field,
                language_code=language_code,
                fallback=True
            )
            setattr(obj, field, translated_value)

    return obj


def translate_list(objects, language_code=None, fields=None):
    """
    Traduit une liste d'objets

    Args:
        objects: Liste ou queryset d'objets
        language_code: Code de la langue
        fields: Liste des champs

    Returns:
        Liste d'objets traduits
    """
    result = []
    for obj in objects:
        result.append(translate_object(obj, language_code, fields))
    return result
