"""Owner-facing library and board views."""
import shutil
import tempfile

from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings
from django.urls import reverse

from documents.models import BoardMember, Category, Document

User = get_user_model()
_TMP_ROOT = tempfile.mkdtemp(prefix="hartling-lib-test-")


@override_settings(PRIVATE_MEDIA_ROOT=_TMP_ROOT)
class LibraryViewTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.owner = User.objects.create_user(username="owner", password="pw-owner-123")
        cls.minutes = Category.objects.create(name="Minutes", slug="minutes", order=1)
        cls.financials = Category.objects.create(name="Financials", slug="financials", order=2)
        for title, category, summary in [
            ("AGM 2026 Minutes", cls.minutes, "Annual general meeting"),
            ("Budget 2026", cls.financials, "Operating budget"),
            ("Insurance Certificate", None, "Cover note"),
        ]:
            Document.objects.create(
                title=title,
                category=category,
                summary=summary,
                published=True,
                file=SimpleUploadedFile(f"{title}.pdf", b"%PDF x", content_type="application/pdf"),
            )
        Document.objects.create(
            title="Unpublished Draft",
            published=False,
            file=SimpleUploadedFile("d.pdf", b"%PDF d", content_type="application/pdf"),
        )

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(_TMP_ROOT, ignore_errors=True)
        super().tearDownClass()

    def test_library_requires_login(self):
        response = self.client.get(reverse("documents:library"))
        self.assertEqual(response.status_code, 302)
        self.assertIn("/accounts/login/", response.url)

    def _titles(self, response):
        """Every document title rendered, across sections and collections."""
        titles = []
        for group in response.context["sections"]:
            for item in list(group["items"]) + list(group["more"]):
                if item["kind"] == "collection":
                    titles += [d.title for d in item["documents"]]
                else:
                    titles.append(item["document"].title)
        return titles

    def test_library_lists_only_published(self):
        self.client.force_login(self.owner)
        response = self.client.get(reverse("documents:library"), {"group": "all"})
        self.assertEqual(response.status_code, 200)
        titles = self._titles(response)
        self.assertIn("AGM 2026 Minutes", titles)
        self.assertNotIn("Unpublished Draft", titles)

    def test_every_row_carries_searchable_text(self):
        """Search runs in the browser, so each row must expose the text it
        searches on — title, summary, category and tags."""
        self.client.force_login(self.owner)
        body = self.client.get(reverse("documents:library"), {"group": "all"}).content.decode()
        self.assertIn('data-search="agm 2026 minutes', body.lower())
        self.assertIn("annual general meeting", body.lower())  # the summary
        self.assertIn("minutes", body.lower())                 # the category

    @override_settings(SHOW_DOCUMENT_DATES=True)
    def test_category_filter(self):
        self.client.force_login(self.owner)
        response = self.client.get(reverse("documents:library"), {"group": "date", "category": "financials"})
        self.assertEqual(self._titles(response), ["Budget 2026"])

    def test_collections_group_their_documents(self):
        from documents.models import Collection

        collection = Collection.objects.create(
            title="2026 AGM documents", slug="2026-agm-documents"
        )
        Document.objects.filter(title="AGM 2026 Minutes").update(collection=collection)
        self.client.force_login(self.owner)
        response = self.client.get(reverse("documents:library"), {"group": "all"})
        grouped = [
            item for group in response.context["sections"]
            for item in list(group["items"]) + list(group["more"])
            if item["kind"] == "collection"
        ]
        self.assertEqual(len(grouped), 1)
        self.assertEqual(grouped[0]["collection"].title, "2026 AGM documents")
        self.assertEqual(grouped[0]["count"], 1)

    def test_board_requires_login_and_lists_active(self):
        BoardMember.objects.create(name="A Chair", title="Chair", active=True)
        BoardMember.objects.create(name="Former Member", active=False)
        self.assertEqual(self.client.get(reverse("documents:board")).status_code, 302)
        self.client.force_login(self.owner)
        response = self.client.get(reverse("documents:board"))
        names = [m.name for m in response.context["members"]]
        self.assertEqual(names, ["A Chair"])

    def test_board_shows_contact_email_to_signed_in_owners(self):
        """Owners see how to contact their board — the page is behind login."""
        BoardMember.objects.create(
            name="A Chair", title="Chair", contact_email="chair@example.com", active=True
        )
        self.client.force_login(self.owner)
        response = self.client.get(reverse("documents:board"))
        self.assertContains(response, "chair@example.com")

    def test_board_contact_email_is_not_public(self):
        """...but never to someone who has not signed in."""
        BoardMember.objects.create(
            name="A Chair", title="Chair", contact_email="chair@example.com", active=True
        )
        response = self.client.get(reverse("documents:board"))
        self.assertEqual(response.status_code, 302)
        self.assertNotContains(response, "chair@example.com", status_code=302)


@override_settings(PRIVATE_MEDIA_ROOT=_TMP_ROOT)
class GroupingTests(TestCase):
    """Grouping, and the filter that changes with it."""

    @classmethod
    def setUpTestData(cls):
        from datetime import date, timedelta
        cls.today = date.today()
        cls.owner = User.objects.create_user(username="owner", password="pw-owner-123")
        cls.minutes = Category.objects.create(name="Minutes", slug="minutes", order=1)
        cls.money = Category.objects.create(name="Financials", slug="financials", order=2)

        def doc(title, category, when):
            return Document.objects.create(
                title=title, category=category, published=True, published_date=when,
                file=SimpleUploadedFile(f"{title}.pdf", b"%PDF", content_type="application/pdf"))

        # Three recent, spread across two categories; plus older ones.
        cls.recent = cls.today - timedelta(days=30)
        doc("Recent minutes", cls.minutes, cls.recent)
        doc("Recent budget", cls.money, cls.recent)
        doc("Last year minutes", cls.minutes, date(cls.today.year - 1, 6, 1))
        doc("Old minutes", cls.minutes, date(cls.today.year - 4, 6, 1))

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(_TMP_ROOT, ignore_errors=True)
        super().tearDownClass()

    def setUp(self):
        self.client.force_login(self.owner)

    def test_the_default_view_is_category_across_all_time(self):
        """Everything by default: a past-year default left the quieter
        properties showing two documents out of a hundred."""
        response = self.client.get(reverse("documents:library"))
        self.assertEqual(response.context["group_by"], "category")
        self.assertEqual(response.context["when"], "all")
        titles = [i["document"].title for s in response.context["sections"]
                  for i in list(s["items"]) + list(s["more"])]
        self.assertIn("Recent minutes", titles)
        self.assertIn("Old minutes", titles)

    @override_settings(SHOW_DOCUMENT_DATES=True)
    def test_the_past_year_filter_narrows_it(self):
        response = self.client.get(reverse("documents:library"), {"when": "recent"})
        titles = [i["document"].title for s in response.context["sections"] for i in s["items"]]
        self.assertIn("Recent minutes", titles)
        self.assertNotIn("Old minutes", titles)

    @override_settings(SHOW_DOCUMENT_DATES=True)
    def test_last_year_filter(self):
        response = self.client.get(reverse("documents:library"), {"when": "last"})
        titles = [i["document"].title for s in response.context["sections"] for i in s["items"]]
        self.assertEqual(titles, ["Last year minutes"])

    def test_a_short_view_is_shown_whole(self):
        """Eight or fewer and nothing is hidden behind a toggle."""
        response = self.client.get(reverse("documents:library"))
        self.assertLessEqual(response.context["shown_total"], 8)
        self.assertEqual(sum(len(s["more"]) for s in response.context["sections"]), 0)

    def test_a_long_view_collapses_to_two_per_category(self):
        from datetime import timedelta
        for n in range(8):
            Document.objects.create(
                title=f"Filler {n}", category=self.minutes, published=True,
                published_date=self.today - timedelta(days=n + 1),
                file=SimpleUploadedFile(f"f{n}.pdf", b"%PDF", content_type="application/pdf"))
        response = self.client.get(reverse("documents:library"))
        self.assertGreater(response.context["shown_total"], 8)
        minutes = [s for s in response.context["sections"] if s["label"] == "Minutes"][0]
        self.assertEqual(len(minutes["items"]), 2)
        self.assertGreater(len(minutes["more"]), 0)

    def test_the_category_pill_is_hidden_in_the_category_view(self):
        response = self.client.get(reverse("documents:library"))
        self.assertTrue(response.context["hide_category"])
        self.assertNotContains(response, 'class="tag has-colour"')

    def test_the_category_pill_shows_only_in_the_flat_view(self):
        """Rule change (2026-08-28): grouped views repeat the category around
        the row — the section heading, or the rail pick — so the pill is
        reserved for the flat list, the one place rows of mixed categories
        sit side by side unlabelled."""
        response = self.client.get(reverse("documents:library"), {"group": "date"})
        self.assertTrue(response.context["hide_category"])
        response = self.client.get(reverse("documents:library"), {"group": "all"})
        self.assertFalse(response.context["hide_category"])
        self.assertContains(response, 'class="tag has-colour"')

    @override_settings(SHOW_DOCUMENT_DATES=True)
    def test_date_view_groups_by_year_and_filters_by_category(self):
        response = self.client.get(reverse("documents:library"),
                                   {"group": "date", "category": "financials"})
        titles = [i["document"].title for s in response.context["sections"] for i in s["items"]]
        self.assertEqual(titles, ["Recent budget"])

    def test_everything_is_one_flat_section(self):
        response = self.client.get(reverse("documents:library"), {"group": "all"})
        self.assertEqual(len(response.context["sections"]), 1)
        self.assertEqual(response.context["sections"][0]["total"], 4)

    def test_unknown_values_fall_back_to_the_defaults(self):
        response = self.client.get(reverse("documents:library"),
                                   {"group": "nonsense", "when": "nonsense"})
        self.assertEqual(response.context["group_by"], "category")
        self.assertEqual(response.context["when"], "all")

    def test_every_category_gets_a_distinct_colour(self):
        self.assertTrue(self.minutes.colour.startswith("#"))
        self.assertNotEqual(self.minutes.colour, self.money.colour)


@override_settings(PRIVATE_MEDIA_ROOT=_TMP_ROOT)
class SplitCollectionTests(TestCase):
    """One batch spans categories; the category view shows it under each."""

    @classmethod
    def setUpTestData(cls):
        cls.owner = User.objects.create_user(username="owner", password="pw-owner-123")
        cls.minutes = Category.objects.create(name="Minutes", slug="minutes", order=1)
        cls.money = Category.objects.create(name="Financials", slug="financials", order=2)
        from documents.models import Collection
        cls.agm = Collection.objects.create(
            title="2026 AGM documents", slug="agm-2026", occurred_on="2026-05-13")
        for title, category in (
            ("AGM Minutes", cls.minutes),
            ("Financial Statements", cls.money),
            ("Operating Budget", cls.money),
        ):
            Document.objects.create(
                title=title, category=category, collection=cls.agm, published=True,
                published_date="2026-05-13",
                file=SimpleUploadedFile(f"{title}.pdf", b"%PDF", content_type="application/pdf"))

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(_TMP_ROOT, ignore_errors=True)
        super().tearDownClass()

    def setUp(self):
        self.client.force_login(self.owner)

    def test_the_batch_appears_under_each_category_it_spans(self):
        response = self.client.get(reverse("documents:library"), {"group": "category"})
        sections = {s["label"]: s for s in response.context["sections"]}
        self.assertIn("Minutes", sections)
        self.assertIn("Financials", sections)

        in_minutes = sections["Minutes"]["items"][0]
        in_money = sections["Financials"]["items"][0]
        self.assertEqual(in_minutes["collection"], self.agm)
        self.assertEqual(in_money["collection"], self.agm)

    def test_each_appearance_holds_only_that_category_s_documents(self):
        response = self.client.get(reverse("documents:library"), {"group": "category"})
        sections = {s["label"]: s for s in response.context["sections"]}
        minutes_docs = [d.title for d in sections["Minutes"]["items"][0]["documents"]]
        money_docs = [d.title for d in sections["Financials"]["items"][0]["documents"]]
        self.assertEqual(minutes_docs, ["AGM Minutes"])
        self.assertEqual(sorted(money_docs), ["Financial Statements", "Operating Budget"])

    def test_a_partial_appearance_says_so(self):
        response = self.client.get(reverse("documents:library"), {"group": "category"})
        sections = {s["label"]: s for s in response.context["sections"]}
        self.assertTrue(sections["Minutes"]["items"][0]["partial"])
        self.assertContains(response, "of 3 in this batch")

    @override_settings(SHOW_DOCUMENT_DATES=True)
    def test_the_date_view_keeps_the_batch_whole(self):
        response = self.client.get(reverse("documents:library"), {"group": "date"})
        items = [i for s in response.context["sections"] for i in s["items"]]
        collections = [i for i in items if i["kind"] == "collection"]
        self.assertEqual(len(collections), 1)
        self.assertEqual(collections[0]["count"], 3)
        self.assertFalse(collections[0]["partial"])
