from django.conf import settings
from django.contrib import messages
from django.core import signing
from django.core.cache import cache
from django.core.paginator import Paginator
from django.db.models import Count, F, Prefetch, Q
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render

from apps.core.models import SiteSettings
from apps.core.ratelimit import client_ip
from apps.engagement.forms import CommentForm
from apps.engagement.models import Comment, CommentVote, Reaction
from apps.taxonomy.models import Category, Tag

from ..models import Annonce, Article

PER_PAGE = 12
BOT_MARKERS = ("bot", "crawl", "spider", "slurp", "facebookexternalhit", "preview")


def _published_articles():
    return Article.objects.published().select_related("auteur", "categorie", "image_principale")


def home(request):
    data = cache.get("public.home")
    if data is None:
        site = SiteSettings.load()
        qs = _published_articles()
        une = list(qs.filter(est_a_la_une=True)[:5])
        if not une:
            une = list(qs[:5])
        investigation_cats = list(site.investigation_categories.all()) or list(
            Category.objects.filter(nom__in=["Société", "Politique", "Environnement"])
        )
        data = {
            "a_la_une": une,
            "derniers": list(qs.exclude(pk__in=[a.pk for a in une[:1]])[:12]),
            "populaires": list(qs.order_by("-vues")[:6]),
            "investigations": list(qs.filter(categorie__in=investigation_cats)[:3]) if investigation_cats else [],
            "annonces": list(Annonce.objects.published().select_related("image_principale")[:4]),
            "rubriques": [
                (cat, list(qs.filter(categorie=cat)[:4]))
                for cat in Category.objects.in_menu()[:4]
            ],
        }
        cache.set("public.home", data, 120)
    return render(request, "public/home.html", data)


def _listing(request, qs, **ctx):
    page = Paginator(qs, PER_PAGE).get_page(request.GET.get("page"))
    ctx.update(page_obj=page, populaires=_published_articles().order_by("-vues")[:5])
    return render(request, "public/article_list.html", ctx)


def article_list(request):
    return _listing(request, _published_articles(), title="Toute l'actualité", kicker="Actualités")


def category(request, slug):
    cat = get_object_or_404(Category, slug=slug)
    return _listing(request, _published_articles().filter(categorie=cat), category=cat, title=cat.nom,
                    kicker="Rubrique", description=cat.description)


def tag(request, slug):
    t = get_object_or_404(Tag, slug=slug)
    return _listing(request, _published_articles().filter(tags=t), title=f"#{t.nom}", kicker="Mot-clé")


def search(request):
    q = (request.GET.get("q") or "").strip()
    if not q:
        messages.error(request, "Veuillez saisir un terme de recherche.")
        return redirect("public:articles")
    qs = _published_articles().filter(Q(titre__icontains=q) | Q(resume__icontains=q) | Q(contenu__icontains=q))
    return _listing(request, qs, title=f"Résultats pour « {q} »", kicker="Recherche", query=q)


def _count_view(request, article):
    ua = request.META.get("HTTP_USER_AGENT", "").lower()
    if not ua or any(m in ua for m in BOT_MARKERS):
        return
    seen = request.session.get("seen_articles", [])
    if article.pk in seen:
        return
    Article.objects.filter(pk=article.pk).update(vues=F("vues") + 1)
    request.session["seen_articles"] = (seen + [article.pk])[-200:]


def _comments(request, article):
    replies = Comment.objects.approved().select_related("user").annotate(
        up=Count("votes", filter=Q(votes__vote="up")), down=Count("votes", filter=Q(votes__vote="down"))
    ).order_by("created_at")
    comments = list(
        Comment.objects.approved().filter(article=article, parent__isnull=True).select_related("user")
        .annotate(up=Count("votes", filter=Q(votes__vote="up")), down=Count("votes", filter=Q(votes__vote="down")))
        .prefetch_related(Prefetch("replies", queryset=replies, to_attr="approved_replies"))
        .order_by("-created_at")
    )
    ids = [c.pk for c in comments] + [r.pk for c in comments for r in c.approved_replies]
    ident = {"user": request.user} if request.user.is_authenticated else {"user__isnull": True, "ip_address": client_ip(request) or None}
    my_votes = dict(CommentVote.objects.filter(comment_id__in=ids, **ident).values_list("comment_id", "vote"))
    return comments, my_votes


def article_context(request, article):
    comments, my_votes = _comments(request, article)
    ident = {"user": request.user} if request.user.is_authenticated else {"user__isnull": True, "ip_address": client_ip(request) or None}
    base = _published_articles().exclude(pk=article.pk)
    return {
        "article": article,
        "gallery": article.images.select_related("media"),
        "tags": article.tags.all(),
        "similaires": base.filter(categorie=article.categorie_id)[:3],
        "du_meme_auteur": base.filter(auteur=article.auteur_id)[:4],
        "populaires": base.order_by("-vues")[:5],
        "comments": comments,
        "comment_count": sum(1 + len(c.approved_replies) for c in comments),
        "my_votes": my_votes,
        "likes": article.reactions.count(),
        "has_liked": Reaction.objects.filter(article=article, **ident).exists(),
        "comment_form": CommentForm(user=request.user),
    }


def render_article(request, article, preview=None):
    ctx = article_context(request, article)
    ctx["preview"] = preview
    return render(request, "public/article_detail.html", ctx)


def article_detail(request, slug):
    article = get_object_or_404(_published_articles(), slug=slug)
    _count_view(request, article)
    return render_article(request, article)


def article_next(request, pk):
    """Article précédent dans le temps, chargé en fin de lecture (défilement continu)."""
    current = get_object_or_404(Article.objects.published(), pk=pk)
    nxt = _published_articles().filter(publie_le__lt=current.publie_le).first()
    if not nxt:
        return HttpResponse("")
    _count_view(request, nxt)
    ctx = article_context(request, nxt)
    ctx["is_continuation"] = True
    return render(request, "public/_article_body.html", ctx)


# --- Annonces -------------------------------------------------------------------

def annonce_list(request):
    qs = Annonce.objects.published().select_related("image_principale", "piece_jointe")
    current = request.GET.get("type", "")
    if current in Annonce.Type.values:
        qs = qs.filter(type=current)
    page = Paginator(qs, PER_PAGE).get_page(request.GET.get("page"))
    return render(request, "public/annonce_list.html", {"page_obj": page, "types": Annonce.Type.choices, "current_type": current})


def render_annonce(request, annonce, preview=None):
    others = Annonce.objects.published().exclude(pk=annonce.pk).select_related("image_principale")[:4]
    return render(request, "public/annonce_detail.html", {"annonce": annonce, "others": others, "preview": preview})


def annonce_detail(request, slug):
    annonce = get_object_or_404(Annonce.objects.published().select_related("piece_jointe", "image_principale"), slug=slug)
    return render_annonce(request, annonce)


# --- Aperçu partagé -------------------------------------------------------------

def preview(request, token):
    from .manage import PREVIEW_SALT

    try:
        data = signing.loads(token, salt=PREVIEW_SALT, max_age=settings.LABELINFO["PREVIEW_LINK_MAX_AGE"])
    except signing.BadSignature:
        raise Http404("Lien d'aperçu invalide ou expiré.")
    model = {"article": Article, "annonce": Annonce}.get(data.get("m"))
    if not model:
        raise Http404
    obj = get_object_or_404(model.objects.alive(), pk=data.get("pk"))
    info = {"share_url": request.build_absolute_uri(), "edit_url": None, "shared": True}
    return render_article(request, obj, info) if model is Article else render_annonce(request, obj, info)
