from __future__ import annotations

from django.http import JsonResponse
from django.views.generic import ListView, TemplateView, View

from apps.core.lists.pagination import paginate_qs
from apps.repository.analytics import services as analytics_services
from apps.repository.search import services as search_services

SAMPLE_QUERIES_FALLBACK = ("climate", "maize", "post-harvest", "agroforestry", "policy")

# Advanced-search fields that carry an actual query (page/sort are navigation).
ADVANCED_FIELDS = (
    "title", "abstract", "author", "author_orcid", "doi", "license",
    "year_from", "year_to", "document_type", "collection", "subject", "grant",
)


def _safe_int(value):
    """Parse a query-string year into an int, tolerating blanks and junk."""
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


class SearchView(ListView):
    template_name = "search/search.html"
    paginate_by = 25
    context_object_name = "object_list"

    def get_queryset(self):
        params = self.request.GET
        self._search_filters = {
            "document_type": params.get("type") or None,
            "collection": params.get("collection") or None,
            "author": params.get("author") or None,
            "year_from": params.get("year_from") or None,
            "year_to": params.get("year_to") or None,
            "language": params.get("language") or None,
            "license": params.get("license") or None,
            "grant_reference": params.get("grant") or None,
            "region": params.get("region") or None,
            "country": params.get("country") or None,
            "subject": params.get("subject") or None,
            "publisher": params.get("publisher") or None,
            "conference": params.get("conference") or None,
        }
        qs = search_services.search_documents(
            query=params.get("q", ""),
            filters=self._search_filters,
            sort=params.get("sort", "relevance"),
            user=self.request.user,
        )
        return qs

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        query = self.request.GET.get("q", "")
        ctx["q"] = query
        ctx["view"] = "grid" if self.request.GET.get("view") == "grid" else "list"

        # FRREP-SR004 — expose type/collection/language as real filters on the
        # Search page itself (previously only Browse had a dynamic filter rail).
        from apps.repository.documents.forms import LANGUAGE_CHOICES
        from apps.repository.documents.models import Collection, Document

        ctx["document_type_choices"] = Document.DocumentType.choices
        ctx["collection_choices"] = (
            Collection.objects.exclude(visibility=Collection.Visibility.INTERNAL)
            .order_by("name")
        )
        ctx["language_choices"] = LANGUAGE_CHOICES
        # FRREP-SR004 — remaining filter controls: licence, subject (free text),
        # institution (publisher proxy) and grant reference.
        ctx["license_choices"] = Document.License.choices
        from django.db.models import Count

        ctx["publisher_choices"] = list(
            Document.objects.for_user(self.request.user)
            .exclude(publisher="")
            .values_list("publisher", flat=True)
            .annotate(c=Count("id"))
            .order_by("publisher")
        )
        secondary = ("year_from", "year_to", "subject", "license", "publisher", "grant")
        ctx["advanced_filters_active"] = any(
            (self.request.GET.get(k) or "").strip() for k in secondary
        )

        if not ctx.get("object_list"):
            try:
                rows = analytics_services.get_top_queries(limit=5)
                queries = [row["query"] for row in rows if row.get("query")]
            except Exception:  # noqa: BLE001 — analytics is best-effort here
                queries = []
            ctx["top_queries"] = queries or list(SAMPLE_QUERIES_FALLBACK)
        # FRREP-SR / DCI017 — record the query + active filters + hit count in
        # the search audit trail, but only when the user actually searched (skip
        # the bare landing render). Done LAST so this request's own event doesn't
        # leak into the top_queries empty-state above. Best-effort (try/except).
        active_filters = {k: v for k, v in getattr(self, "_search_filters", {}).items() if v}
        ctx["search_event_id"] = None
        if query.strip() or active_filters:
            paginator = ctx.get("paginator")
            result_count = paginator.count if paginator is not None else len(ctx.get("object_list") or [])
            # FRREP-AR011 — thread the SearchEvent id onto result links so a
            # later click-through to a document detail page can be attributed
            # back to this search (see DocumentDetailView.get / `?se=`).
            ctx["search_event_id"] = search_services.track_search_event(
                query=query,
                filters=active_filters,
                result_count=result_count,
                user=self.request.user,
                request=self.request,
            )
        return ctx

    def get_template_names(self):
        if getattr(self.request, "htmx", False):
            return ["search/partials/search_results.html"]
        return [self.template_name]


class IncrementalSearchView(View):
    def get(self, request):
        prefix = request.GET.get("q", "")
        return JsonResponse({"suggestions": search_services.incremental_search(prefix, user=request.user)})


class AdvancedSearchView(TemplateView):
    template_name = "search/advanced.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        params = self.request.GET

        # FRREP-SR005 — document type / collection / subject-keyword fields.
        from apps.repository.documents.models import Collection, Document, Keyword

        ctx["document_type_choices"] = Document.DocumentType.choices
        ctx["collection_choices"] = (
            Collection.objects.exclude(visibility=Collection.Visibility.INTERNAL)
            .order_by("name")
        )
        ctx["subject_choices"] = (
            Keyword.objects.filter(status=Keyword.Status.APPROVED).order_by("label")
        )

        # A search ran only when at least one real field has a non-blank value —
        # ignore navigation params (page/sort) and empty inputs so an empty form
        # submit doesn't run a full-table scan or render a misleading "0 results".
        has_query = any((params.get(k) or "").strip() for k in ADVANCED_FIELDS)
        if has_query:
            results = search_services.advanced_search(
                title=params.get("title", "").strip(),
                abstract=params.get("abstract", "").strip(),
                author=params.get("author", "").strip(),
                author_orcid=params.get("author_orcid", "").strip(),
                doi=params.get("doi", "").strip(),
                license=params.get("license", "").strip(),
                year_from=_safe_int(params.get("year_from")),
                year_to=_safe_int(params.get("year_to")),
                document_type=params.get("document_type", "").strip(),
                collection=params.get("collection", "").strip(),
                subject=params.get("subject", "").strip(),
                grant=params.get("grant", "").strip(),
                user=self.request.user,
            )
            page_obj = paginate_qs(self.request, results, per_page=25)
            ctx["results"] = page_obj
            ctx["page_obj"] = page_obj
            # FRREP-SR / DCI017 — advanced searches join the same audit trail.
            advanced_filters = {
                k: params.get(k)
                for k in ADVANCED_FIELDS
                if (params.get(k) or "").strip()
            }
            ctx["search_event_id"] = search_services.track_search_event(
                query=params.get("title", "") or params.get("abstract", ""),
                filters=advanced_filters,
                result_count=page_obj.paginator.count,
                user=self.request.user,
                request=self.request,
            )
        return ctx
