"""Postgres FTS over AlumniProfile (FRALU032 / FRALU034 / partial FRALU035).

Mirrors the pattern in ``apps.repository.search.services.search_documents``:
``SearchQuery(q, search_type='websearch')`` natively supports the boolean
operators users expect (``foo OR bar``, ``-baz``, ``"exact phrase"``).
"""
from __future__ import annotations

from django.contrib.postgres.search import SearchQuery, SearchRank
from django.db.models import F, Q

from apps.alumni.profiles.models import AlumniProfile


def search_alumni(
    *,
    query: str = "",
    country: str = "",
    sector: str = "",
    discipline: str = "",
    viewer=None,
    mode: str = "fts",
):
    """Search opted-in alumni.

    ``mode='fts'`` runs the Postgres websearch tsvector path (boolean +
    keyword). ``mode='semantic'`` ranks by vector similarity using the
    embeddings persisted by :mod:`apps.alumni.profiles.embeddings`. The
    semantic path falls back to FTS when no embeddings or no API key are
    available, so the directory always returns *something*.
    """
    if mode == "semantic" and (query or "").strip():
        from apps.alumni.profiles.embeddings import semantic_search_alumni

        results = semantic_search_alumni(
            query=query,
            country=country,
            sector=sector,
            discipline=discipline,
        )
        if results:
            return results

    qs = AlumniProfile.objects.filter(visibility_consent=True).select_related(
        "user", "current_institution"
    )
    if country:
        qs = qs.filter(user__profile__country__iexact=country)
    if sector:
        qs = qs.filter(employments__sector__icontains=sector).distinct()
    if discipline:
        qs = qs.filter(expertise_tags__contains=[discipline.lower()])

    q = (query or "").strip()
    if q:
        sq = SearchQuery(q, search_type="websearch")
        icontains_match = (
            Q(user__first_name__icontains=q)
            | Q(user__last_name__icontains=q)
            | Q(user__email__icontains=q)
            | Q(headline__icontains=q)
            | Q(current_employer__icontains=q)
            | Q(current_position__icontains=q)
        )
        # FTS gives us ranking; icontains is the safety net for rows whose
        # search_vector hasn't been populated (e.g. older seeded data) or
        # for prefix-style queries that websearch misses.
        qs = (
            qs.filter(icontains_match | Q(search_vector=sq))
            .annotate(rank=SearchRank(F("search_vector"), sq))
            .distinct()
        )
        return qs.order_by("-rank", "-published_at", "user__last_name")

    return qs.order_by("-published_at", "user__last_name")
