"""Deterministic analytics services."""

from __future__ import annotations

import pytest
from django.utils import timezone

from apps.repository.analytics import services as analytics_services
from apps.repository.documents import services as document_services
from apps.repository.documents.models import Document


@pytest.mark.django_db
def test_record_access_event_persists(submitter_user, public_collection, sample_pdf, rf):
    doc = document_services.ingest_document(
        file=sample_pdf,
        title="x",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    request = rf.get("/repository/")
    analytics_services.record_access_event(doc, submitter_user, request)
    assert doc.access_events.count() == 1


@pytest.mark.django_db
def test_record_access_event_dedups_repeat_hits_from_same_viewer(
    submitter_user, manager_user, public_collection, sample_pdf, rf
):
    """One logical view fans out into several GETs (detail + reader + iframe +
    OER hop); they must collapse to a single AccessEvent per viewer/window
    (Search & Retrieval FR#17). A different viewer still counts separately."""
    doc = document_services.ingest_document(
        file=sample_pdf,
        title="dedup views",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    request = rf.get("/repository/")
    for _ in range(6):
        analytics_services.record_access_event(doc, submitter_user, request)
    assert doc.access_events.count() == 1  # not 6

    analytics_services.record_access_event(doc, manager_user, request)
    assert doc.access_events.count() == 2  # a different viewer counts


@pytest.mark.django_db
def test_record_download_event_dedups_probe_and_range_requests(
    submitter_user, manager_user, public_collection, sample_pdf, rf
):
    """A browser download issues a probe/range second request; it must not
    double-count (Search & Retrieval FR#19)."""
    doc = document_services.ingest_document(
        file=sample_pdf,
        title="dedup downloads",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    request = rf.get("/repository/")
    analytics_services.record_download_event(doc, None, submitter_user, request)
    analytics_services.record_download_event(doc, None, submitter_user, request)
    assert doc.download_events.count() == 1  # not 2

    analytics_services.record_download_event(doc, None, manager_user, request)
    assert doc.download_events.count() == 2  # a different viewer counts


@pytest.mark.django_db
def test_get_usage_dashboard_returns_keys():
    data = analytics_services.get_usage_dashboard()
    for key in ("total_documents", "total_downloads", "open_access_rate", "by_type", "top_documents"):
        assert key in data


@pytest.mark.django_db
def test_get_zero_result_queries_groups_and_counts_excluding_blank():
    from apps.repository.analytics.models import SearchEvent

    SearchEvent.objects.create(query="obscure topic", result_count=0)
    SearchEvent.objects.create(query="obscure topic", result_count=0)
    SearchEvent.objects.create(query="another gap", result_count=0)
    # Has results — should not appear in the zero-result report.
    SearchEvent.objects.create(query="found something", result_count=3)
    # Blank query (filter-only search) with zero results — should be excluded.
    SearchEvent.objects.create(query="", result_count=0, filters={"document_type": "thesis"})

    rows = analytics_services.get_zero_result_queries()

    by_query = {row["query"]: row["count"] for row in rows}
    assert by_query == {"obscure topic": 2, "another gap": 1}
    # Ordered by count descending.
    assert rows[0]["query"] == "obscure topic"


@pytest.mark.django_db
def test_get_zero_result_queries_respects_limit():
    from apps.repository.analytics.models import SearchEvent

    for i in range(5):
        SearchEvent.objects.create(query=f"gap {i}", result_count=0)

    rows = analytics_services.get_zero_result_queries(limit=2)
    assert len(rows) == 2


@pytest.mark.django_db
def test_get_search_conversion_rate_with_mix_of_clicked_and_unclicked(
    submitter_user, public_collection, sample_pdf
):
    from apps.repository.analytics.models import SearchEvent

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="clicked doc",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )

    # Two searches with results and a click-through, one with results but no click.
    SearchEvent.objects.create(query="a", result_count=2, clicked_document=doc)
    SearchEvent.objects.create(query="b", result_count=1, clicked_document=doc)
    SearchEvent.objects.create(query="c", result_count=5, clicked_document=None)
    # Zero-result searches are excluded from the denominator entirely.
    SearchEvent.objects.create(query="d", result_count=0, clicked_document=None)

    data = analytics_services.get_search_conversion_rate()

    assert data["total_searches_with_results"] == 3
    assert data["converted"] == 2
    assert data["conversion_rate"] == pytest.approx(66.7, abs=0.1)


@pytest.mark.django_db
def test_get_search_conversion_rate_handles_no_searches_without_dividing_by_zero():
    data = analytics_services.get_search_conversion_rate()
    assert data == {
        "total_searches_with_results": 0,
        "converted": 0,
        "conversion_rate": 0.0,
    }


@pytest.mark.django_db
def test_usage_dashboard_view_renders_new_panels(client, manager_user):
    from apps.repository.analytics.models import SearchEvent

    SearchEvent.objects.create(query="missing content", result_count=0)
    SearchEvent.objects.create(query="found content", result_count=4, clicked_document=None)

    client.force_login(manager_user)
    response = client.get("/repository/analytics/dashboard/")

    assert response.status_code == 200
    assert "zero_result_queries" in response.context
    assert "search_conversion" in response.context
    content = response.content.decode()
    assert "Zero-result queries" in content
    assert "Search" in content and "click-through" in content


@pytest.mark.django_db
def test_usage_dashboard_view_renders_empty_state_when_no_zero_result_queries(
    client, manager_user
):
    client.force_login(manager_user)
    response = client.get("/repository/analytics/dashboard/")

    assert response.status_code == 200
    assert response.context["zero_result_queries"] == []
    assert "No zero-result searches" in response.content.decode()


# ── FRREP-AR001 — usage dashboard filters ──────────────────────────────────


@pytest.mark.django_db
def test_usage_dashboard_view_threads_get_filters_into_service(
    client, manager_user, public_collection, submitter_user, sample_pdf
):
    from apps.repository.documents import services as document_services

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="Filtered doc",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    Document.objects.filter(pk=doc.pk).update(
        status=Document.Status.PUBLISHED,
        visibility=Document.Visibility.PUBLIC_OPEN,
        published_at=timezone.now(),
    )

    client.force_login(manager_user)
    response = client.get(
        "/repository/analytics/dashboard/",
        {"collection": public_collection.slug, "document_type": Document.DocumentType.THESIS},
    )

    assert response.status_code == 200
    assert response.context["stats"]["total_documents"] == 1
    assert response.context["filter_collection"] == public_collection.slug
    assert response.context["filter_document_type"] == Document.DocumentType.THESIS
    assert response.context["filters_active"] is True


@pytest.mark.django_db
def test_usage_dashboard_view_no_filters_is_unfiltered(client, manager_user):
    client.force_login(manager_user)
    response = client.get("/repository/analytics/dashboard/")

    assert response.status_code == 200
    assert response.context["filters_active"] is False


# ── FRREP-AR004 — Open Access compliance by document type / institution ───


@pytest.mark.django_db
def test_get_open_access_compliance_by_document_type(
    public_collection, submitter_user, sample_pdf
):
    from apps.repository.documents import services as document_services

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="Compliance by type",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    Document.objects.filter(pk=doc.pk).update(
        status=Document.Status.PUBLISHED,
        visibility=Document.Visibility.PUBLIC_OPEN,
        published_at=timezone.now(),
    )

    rows = analytics_services.get_open_access_compliance(by="document_type")
    labels = {r["label"] for r in rows}
    assert "Thesis / Dissertation" in labels
    row = next(r for r in rows if r["label"] == "Thesis / Dissertation")
    assert row["total"] >= 1
    assert row["open_count"] >= 1


@pytest.mark.django_db
def test_get_open_access_compliance_by_institution(
    public_collection, submitter_user, sample_pdf
):
    from apps.core.authentication.models import Institution
    from apps.repository.documents import services as document_services
    from apps.repository.documents.models import Author

    inst = Institution.objects.create(name="Compliance Test University")
    author = Author.objects.create(first_name="A", last_name="B", institution=inst)

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="Compliance by institution",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    doc.authors.add(author)
    Document.objects.filter(pk=doc.pk).update(
        status=Document.Status.PUBLISHED,
        visibility=Document.Visibility.PUBLIC_OPEN,
        published_at=timezone.now(),
    )

    rows = analytics_services.get_open_access_compliance(by="institution")
    row = next(r for r in rows if r["label"] == "Compliance Test University")
    assert row["total"] == 1
    assert row["open_count"] == 1
    assert row["rate"] == 100.0


@pytest.mark.django_db
def test_compliance_report_view_dimension_switcher(client, manager_user):
    client.force_login(manager_user)
    response = client.get("/repository/analytics/compliance/", {"by": "document_type"})

    assert response.status_code == 200
    assert response.context["by"] == "document_type"
    assert "Document type" in response.content.decode()


@pytest.mark.django_db
def test_compliance_report_view_rejects_unknown_dimension(client, manager_user):
    client.force_login(manager_user)
    response = client.get("/repository/analytics/compliance/", {"by": "not_a_real_dimension"})

    assert response.status_code == 200
    assert response.context["by"] == "collection"


# ── FRREP-AR005 — collection analytics: most-accessed + cumulative growth ─


@pytest.mark.django_db
def test_get_collection_analytics_most_accessed_combines_views_and_downloads(
    public_collection, submitter_user, sample_pdf
):
    from apps.repository.analytics.models import AccessEvent, DownloadEvent
    from apps.repository.documents import services as document_services

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="Popular doc",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    Document.objects.filter(pk=doc.pk).update(
        status=Document.Status.PUBLISHED, published_at=timezone.now()
    )
    doc = Document.objects.get(pk=doc.pk)

    AccessEvent.objects.create(document=doc)
    AccessEvent.objects.create(document=doc)
    AccessEvent.objects.create(document=doc)
    DownloadEvent.objects.create(document=doc)

    data = analytics_services.get_collection_analytics(public_collection)
    assert data["most_accessed"]
    top = data["most_accessed"][0]
    assert top["document__title"] == "Popular doc"
    assert top["views"] == 3
    assert top["downloads"] == 1
    assert top["score"] == 3 + 1 * 2


@pytest.mark.django_db
def test_get_collection_analytics_growth_series_is_cumulative(
    public_collection, submitter_user, sample_pdf
):
    from apps.repository.documents import services as document_services

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="Growth doc",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    Document.objects.filter(pk=doc.pk).update(
        status=Document.Status.PUBLISHED, published_at=timezone.now()
    )

    data = analytics_services.get_collection_analytics(public_collection)
    assert data["growth_series"]
    # Cumulative — totals never decrease across the series.
    totals = [p["total"] for p in data["growth_series"]]
    assert totals == sorted(totals)
    assert totals[-1] >= 1


# ── FRREP-AR011 — search click-through recording ───────────────────────────


@pytest.mark.django_db
def test_record_search_event_returns_pk():
    pk = analytics_services.record_search_event(
        query="q", filters={}, result_count=1, clicked_doc=None, user=None, request=None
    )
    from apps.repository.analytics.models import SearchEvent

    assert SearchEvent.objects.filter(pk=pk).exists()


@pytest.mark.django_db
def test_record_search_click_sets_clicked_document(public_collection, submitter_user, sample_pdf):
    from apps.repository.analytics.models import SearchEvent
    from apps.repository.documents import services as document_services

    doc = document_services.ingest_document(
        file=sample_pdf,
        title="Clicked via search",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="y",
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    event = SearchEvent.objects.create(query="q", result_count=1)

    ok = analytics_services.record_search_click(event.pk, doc)

    assert ok is True
    assert SearchEvent.objects.get(pk=event.pk).clicked_document_id == doc.pk


@pytest.mark.django_db
def test_record_search_click_ignores_garbage_id():
    assert analytics_services.record_search_click("not-an-id", None) is False
    assert analytics_services.record_search_click(None, None) is False
