"""Unit tests for SortableListMixin + CsvExportMixin.

Uses ``StrategicObjective`` as the backing model — small, no FKs, three fields
suffice to exercise asc/desc/unknown-key/CSV paths.
"""

from __future__ import annotations

import csv as csv_module
import io

import pytest
from django.test import RequestFactory
from django.views.generic import ListView

from apps.core.lists import CsvExportMixin, SortableListMixin
from apps.rims.grants.models import StrategicObjective


class _DemoListView(SortableListMixin, CsvExportMixin, ListView):
    model = StrategicObjective
    template_name = "components/empty_state.html"  # any valid template
    paginate_by = 100
    sortable_fields = {"name": "name", "created": "created_at"}
    default_sort = "name"
    csv_columns = (("Name", "name"), ("Slug", "slug"), ("Active", "active"))
    csv_filename = "objectives"


@pytest.fixture
def factory():
    return RequestFactory()


@pytest.fixture
def three_objectives(db):
    StrategicObjective.objects.bulk_create([
        StrategicObjective(name="Bravo", slug="bravo", active=True),
        StrategicObjective(name="Charlie", slug="charlie", active=False),
        StrategicObjective(name="Alpha", slug="alpha", active=True),
    ])


# --- SortableListMixin ------------------------------------------------------


def test_sortable_default_when_no_param(factory):
    view = _DemoListView()
    view.request = factory.get("/")
    assert view.get_ordering() == "name"


def test_sortable_asc(factory):
    view = _DemoListView()
    view.request = factory.get("/?sort=created")
    assert view.get_ordering() == "created_at"


def test_sortable_desc(factory):
    view = _DemoListView()
    view.request = factory.get("/?sort=-created")
    assert view.get_ordering() == "-created_at"


def test_sortable_unknown_key_falls_back(factory):
    view = _DemoListView()
    view.request = factory.get("/?sort=bogus")
    assert view.get_ordering() == "name"


def test_sortable_unknown_desc_key_falls_back(factory):
    view = _DemoListView()
    view.request = factory.get("/?sort=-bogus")
    assert view.get_ordering() == "name"


def test_sortable_context_exposes_state(factory):
    view = _DemoListView()
    view.request = factory.get("/?sort=-created")
    view.kwargs = {}
    view.object_list = StrategicObjective.objects.none()
    ctx = view.get_context_data()
    assert ctx["sort"] == "created"
    assert ctx["sort_dir"] == "desc"
    assert set(ctx["sortable_fields"]) == {"name", "created"}


# --- CsvExportMixin ---------------------------------------------------------


@pytest.mark.django_db
def test_csv_export_returns_text_csv(factory, three_objectives):
    view = _DemoListView.as_view()
    response = view(factory.get("/?export=csv"))
    assert response.status_code == 200
    assert response["Content-Type"].startswith("text/csv")
    assert 'filename="objectives.csv"' in response["Content-Disposition"]


@pytest.mark.django_db
def test_csv_export_row_count_matches_queryset(factory, three_objectives):
    view = _DemoListView.as_view()
    response = view(factory.get("/?export=csv"))
    rows = list(csv_module.reader(io.StringIO(response.content.decode())))
    # header + 3 rows
    assert len(rows) == 4
    assert rows[0] == ["Name", "Slug", "Active"]
    # default sort = name ascending → Alpha first
    assert rows[1][0] == "Alpha"


@pytest.mark.django_db
def test_csv_export_respects_sort_param(factory, three_objectives):
    view = _DemoListView.as_view()
    response = view(factory.get("/?export=csv&sort=-name"))
    rows = list(csv_module.reader(io.StringIO(response.content.decode())))
    assert rows[1][0] == "Charlie"  # desc → Charlie first


@pytest.mark.django_db
def test_csv_empty_queryset_returns_header_only(factory, db):
    view = _DemoListView.as_view()
    response = view(factory.get("/?export=csv"))
    rows = list(csv_module.reader(io.StringIO(response.content.decode())))
    assert len(rows) == 1
    assert rows[0] == ["Name", "Slug", "Active"]


@pytest.mark.django_db
def test_csv_export_disabled_passes_through(factory, three_objectives):
    class _DisabledView(_DemoListView):
        csv_export_disabled = True

    view = _DisabledView.as_view()
    response = view(factory.get("/?export=csv"))
    # CSV intercept skipped → falls through to normal HTML render
    assert response["Content-Type"].startswith("text/html")


def test_non_csv_request_passes_through(factory, db):
    view = _DemoListView.as_view()
    response = view(factory.get("/"))
    assert response["Content-Type"].startswith("text/html")


# --- XLSX export -----------------------------------------------------------


@pytest.mark.django_db
def test_xlsx_export_returns_workbook(factory, three_objectives):
    view = _DemoListView.as_view()
    response = view(factory.get("/?export=xlsx"))
    assert response.status_code == 200
    assert response["Content-Type"] == (
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    assert 'filename="objectives.xlsx"' in response["Content-Disposition"]
    # Body should start with the ZIP magic number (xlsx is a zip container).
    assert response.content[:2] == b"PK"


@pytest.mark.django_db
def test_xlsx_export_row_count_and_header(factory, three_objectives):
    from io import BytesIO

    from openpyxl import load_workbook

    view = _DemoListView.as_view()
    response = view(factory.get("/?export=xlsx"))
    wb = load_workbook(BytesIO(response.content))
    ws = wb.active
    rows = list(ws.iter_rows(values_only=True))
    assert rows[0] == ("Name", "Slug", "Active")
    assert len(rows) == 4  # header + 3
    assert rows[1][0] == "Alpha"  # default sort = name asc


@pytest.mark.django_db
def test_xlsx_export_respects_sort_param(factory, three_objectives):
    from io import BytesIO

    from openpyxl import load_workbook

    view = _DemoListView.as_view()
    response = view(factory.get("/?export=xlsx&sort=-name"))
    wb = load_workbook(BytesIO(response.content))
    rows = list(wb.active.iter_rows(values_only=True))
    assert rows[1][0] == "Charlie"  # desc first


@pytest.mark.django_db
def test_unknown_export_format_passes_through(factory, three_objectives):
    """``?export=foo`` is not a recognised format → falls back to HTML."""
    view = _DemoListView.as_view()
    response = view(factory.get("/?export=foo"))
    assert response["Content-Type"].startswith("text/html")
