"""Editing groups and documents from inside the library."""
import json
import shutil
import tempfile

from django.conf import settings
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 Category, Collection, Document

User = get_user_model()
_TMP = tempfile.mkdtemp(prefix="hartling-edit-test-")


def pdf(name="a.pdf"):
    return SimpleUploadedFile(name, b"%PDF-1.4", content_type="application/pdf")


@override_settings(PRIVATE_MEDIA_ROOT=_TMP)
class EditingTests(TestCase):
    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(_TMP, ignore_errors=True)
        super().tearDownClass()

    def setUp(self):
        self.staff = User.objects.create_user("staff", password="pw-staff-12345", is_staff=True)
        self.owner = User.objects.create_user("owner", password="pw-owner-12345")
        self.minutes = Category.objects.create(name="Minutes", slug="minutes")
        self.financials = Category.objects.create(name="Financials", slug="financials")
        self.group = Collection.objects.create(
            title="Aug 8 uploads", slug="aug-8-uploads", occurred_on="2026-08-08",
        )
        self.doc = Document.objects.create(
            title="Untitled scan", collection=self.group, category=self.minutes,
            published=True, published_date="2026-08-08", file=pdf(),
        )
        self.other = Document.objects.create(
            title="Second paper", collection=self.group, category=self.minutes,
            published=True, published_date="2026-08-08", file=pdf("b.pdf"),
        )

    def _post(self, url, payload):
        return self.client.post(
            url, data=json.dumps(payload), content_type="application/json", secure=True
        )

    # ---- access ----
    def test_owners_cannot_edit_a_group(self):
        self.client.force_login(self.owner)
        response = self._post(
            reverse("backoffice:collection_update", args=[self.group.pk]), {"title": "Hacked"}
        )
        self.assertEqual(response.status_code, 302)
        self.group.refresh_from_db()
        self.assertEqual(self.group.title, "Aug 8 uploads")

    def test_owners_cannot_edit_a_document(self):
        self.client.force_login(self.owner)
        self._post(reverse("backoffice:document_update", args=[self.doc.pk]), {"title": "Hacked"})
        self.doc.refresh_from_db()
        self.assertEqual(self.doc.title, "Untitled scan")

    def test_anonymous_cannot_edit(self):
        response = self._post(
            reverse("backoffice:document_update", args=[self.doc.pk]), {"title": "Hacked"}
        )
        self.assertEqual(response.status_code, 302)

    # ---- groups ----
    def test_rename_a_group(self):
        self.client.force_login(self.staff)
        response = self._post(reverse("backoffice:collection_update", args=[self.group.pk]),
                              {"title": "2026 AGM documents"})
        self.assertEqual(response.status_code, 200)
        self.group.refresh_from_db()
        self.assertEqual(self.group.title, "2026 AGM documents")

    def test_group_date_moves_its_documents(self):
        """The rule from the uploader holds after the fact too."""
        self.client.force_login(self.staff)
        self._post(reverse("backoffice:collection_update", args=[self.group.pk]),
                   {"title": "Aug 8 uploads", "date": "2026-05-13", "cascade": True})
        self.group.refresh_from_db(); self.doc.refresh_from_db(); self.other.refresh_from_db()
        self.assertEqual(str(self.group.occurred_on), "2026-05-13")
        self.assertEqual(str(self.doc.published_date), "2026-05-13")
        self.assertEqual(str(self.other.published_date), "2026-05-13")

    def test_group_date_can_be_changed_without_moving_documents(self):
        self.client.force_login(self.staff)
        self._post(reverse("backoffice:collection_update", args=[self.group.pk]),
                   {"title": "Aug 8 uploads", "date": "2026-05-13", "cascade": False})
        self.group.refresh_from_db(); self.doc.refresh_from_db()
        self.assertEqual(str(self.group.occurred_on), "2026-05-13")
        self.assertEqual(str(self.doc.published_date), "2026-08-08")

    def test_a_group_has_no_category_to_change(self):
        """Groups deliberately carry no category.

        A batch holds several kinds of document, and in the category view the
        same group is shown under each of them — so "apply this category to the
        group" could never say honestly which documents it meant. Category
        belongs to the document.
        """
        self.assertFalse(hasattr(self.group, "category"))

        self.client.force_login(self.staff)
        self._post(reverse("backoffice:collection_update", args=[self.group.pk]),
                   {"title": "Aug 8 uploads", "category": self.financials.pk})
        # A category sent anyway is ignored, not applied to the documents.
        self.doc.refresh_from_db(); self.other.refresh_from_db()
        self.assertEqual(self.doc.category, self.minutes)
        self.assertEqual(self.other.category, self.minutes)

    def test_the_group_editor_hides_the_category_field(self):
        """The row is in the markup for documents; the JS hides it for groups.

        Hiding is done with the `hidden` attribute, so the stylesheet has to
        make that attribute beat `display` — `.field` is `display: grid`, which
        silently outranked `hidden` and left the field on screen.
        """
        self.client.force_login(self.staff)
        body = self.client.get(reverse("documents:library"), secure=True).content.decode()
        self.assertIn("category-row", body)
        self.assertIn("getElementById('category-row').hidden = isGroup", body)
        css = (settings.BASE_DIR / "static" / "portal.css").read_text()
        self.assertIn("[hidden] { display: none !important; }", css)

    def test_the_library_renders_each_dialog_exactly_once(self):
        """Duplicated markup is silent until something fires twice.

        The staff block was once emitted twice, which gave every element a
        duplicate id: `getElementById` still resolved (to the first copy), so
        the page looked right, but each handler was bound twice and a save
        POSTed twice. Counting the ids is the cheapest way to catch it.
        """
        self.client.force_login(self.staff)
        body = self.client.get(reverse("documents:library"), secure=True).content.decode()
        for element_id in ("edit-dialog", "upload-dialog", "edit-form", "category-row"):
            self.assertEqual(body.count('id="%s"' % element_id), 1, element_id)

    # ---- documents ----
    def test_rename_and_re_date_a_document(self):
        self.client.force_login(self.staff)
        response = self._post(reverse("backoffice:document_update", args=[self.doc.pk]),
                              {"title": "2026 AGM Minutes", "date": "2026-05-13",
                               "category": self.financials.pk})
        self.assertEqual(response.status_code, 200)
        self.doc.refresh_from_db()
        self.assertEqual(self.doc.title, "2026 AGM Minutes")
        self.assertEqual(str(self.doc.published_date), "2026-05-13")
        self.assertEqual(self.doc.category, self.financials)

    def test_a_documents_own_date_does_not_move_its_group(self):
        self.client.force_login(self.staff)
        self._post(reverse("backoffice:document_update", args=[self.doc.pk]),
                   {"title": "Untitled scan", "date": "2020-01-01"})
        self.group.refresh_from_db(); self.other.refresh_from_db()
        self.assertEqual(str(self.group.occurred_on), "2026-08-08")
        self.assertEqual(str(self.other.published_date), "2026-08-08")

    def test_category_can_be_cleared(self):
        self.client.force_login(self.staff)
        self._post(reverse("backoffice:document_update", args=[self.doc.pk]),
                   {"title": "Untitled scan", "category": ""})
        self.doc.refresh_from_db()
        self.assertIsNone(self.doc.category)

    def test_editing_requires_post(self):
        self.client.force_login(self.staff)
        response = self.client.get(
            reverse("backoffice:document_update", args=[self.doc.pk]), secure=True
        )
        self.assertEqual(response.status_code, 405)

    def test_edit_controls_render_for_staff_only(self):
        self.client.force_login(self.staff)
        body = self.client.get(reverse("documents:library"), secure=True).content.decode()
        self.assertIn('data-kind="collection"', body)
        self.assertIn("edit-dialog", body)

        self.client.force_login(self.owner)
        body = self.client.get(reverse("documents:library"), secure=True).content.decode()
        self.assertNotIn('data-kind="collection"', body)
        self.assertNotIn("edit-dialog", body)
