"""The sign-in photograph manager: unlisted, staff-gated, add and remove."""
import io
import shutil
import tempfile

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

from documents.models import LoginPhoto

User = get_user_model()
_TMP = tempfile.mkdtemp(prefix="hartling-scene-mgr-")


def jpeg(width=2400, height=1400, colour="teal"):
    buffer = io.BytesIO()
    Image.new("RGB", (width, height), colour).save(buffer, format="JPEG")
    return buffer.getvalue()


@override_settings(PRIVATE_MEDIA_ROOT=_TMP)
class ScenePhotoTests(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")

    def seed(self, caption="seed.jpg"):
        photo = LoginPhoto(caption=caption)
        photo.image.save(caption, ContentFile(jpeg(60, 40)), save=True)
        return photo

    # ---- unlisted, but not unprotected ----
    def test_owners_cannot_open_the_manager(self):
        self.client.force_login(self.owner)
        response = self.client.get(reverse("backoffice:scene_photos"), secure=True)
        self.assertEqual(response.status_code, 302)

    def test_owners_cannot_add_or_delete(self):
        photo = self.seed()
        self.client.force_login(self.owner)
        self.client.post(reverse("backoffice:scene_add"), secure=True)
        self.client.post(reverse("backoffice:scene_delete", args=[photo.pk]), secure=True)
        self.assertTrue(LoginPhoto.objects.filter(pk=photo.pk).exists())

    def test_owners_cannot_change_which_photographs_open(self):
        photo = self.seed()
        self.client.force_login(self.owner)
        self.client.post(reverse("backoffice:scene_starter", args=[photo.pk]), secure=True)
        photo.refresh_from_db()
        self.assertFalse(photo.is_starter)

    # ---- openers ----
    def test_staff_can_mark_and_unmark_an_opener(self):
        photo = self.seed()
        self.client.force_login(self.staff)
        url = reverse("backoffice:scene_starter", args=[photo.pk])

        self.client.post(url, secure=True)
        photo.refresh_from_db()
        self.assertTrue(photo.is_starter)

        self.client.post(url, secure=True)          # the same control turns it off
        photo.refresh_from_db()
        self.assertFalse(photo.is_starter)

    def test_marking_an_opener_does_not_remove_it_from_the_rotation(self):
        """An opener is 'may go first', not 'only goes first'."""
        from documents.views import opening_url, scene_urls

        keep, open_with = self.seed(), self.seed()
        open_with.is_starter = True
        open_with.save()
        self.assertEqual(len(scene_urls()), 2)
        self.assertEqual(opening_url(),
                         reverse("documents:login_photo", args=[open_with.pk]))
        self.assertIn(reverse("documents:login_photo", args=[keep.pk]), scene_urls())

    def test_the_manager_shows_which_are_openers(self):
        photo = self.seed()
        photo.is_starter = True
        photo.save()
        self.client.force_login(self.staff)
        body = self.client.get(reverse("backoffice:scene_photos"), secure=True).content.decode()
        self.assertIn("scene-badge", body)
        self.assertIn("is-starter", body)

    def test_it_is_linked_from_nowhere(self):
        """Unlisted by design — staff reach it by URL."""
        self.client.force_login(self.staff)
        for name in ("documents:library", "documents:board", "backoffice:people"):
            body = self.client.get(reverse(name), secure=True).content.decode()
            self.assertNotIn(reverse("backoffice:scene_photos"), body, name)

    # ---- adding ----
    def test_staff_can_add_several_at_once_and_they_are_normalised(self):
        self.client.force_login(self.staff)
        self.client.post(reverse("backoffice:scene_add"), {"photos": [
            SimpleUploadedFile("one.jpg", jpeg(2400, 1400), content_type="image/jpeg"),
            SimpleUploadedFile("two.jpg", jpeg(900, 1800, "navy"), content_type="image/jpeg"),
        ]}, secure=True)
        self.assertEqual(LoginPhoto.objects.count(), 2)
        for photo in LoginPhoto.objects.all():
            # Every photograph ends the same shape, so the slideshow never jumps.
            self.assertEqual(Image.open(photo.image.open("rb")).size, (1800, 1200))

    def test_an_unreadable_file_is_named_not_crashed_on(self):
        self.client.force_login(self.staff)
        response = self.client.post(reverse("backoffice:scene_add"), {"photos": [
            SimpleUploadedFile("broken.jpg", b"not an image", content_type="image/jpeg"),
        ]}, secure=True, follow=True)
        self.assertEqual(LoginPhoto.objects.count(), 0)
        self.assertContains(response, "Could not read")

    # ---- removing ----
    def test_staff_can_remove_one(self):
        first, second = self.seed("a.jpg"), self.seed("b.jpg")
        self.client.force_login(self.staff)
        self.client.post(reverse("backoffice:scene_delete", args=[first.pk]), secure=True)
        self.assertEqual(list(LoginPhoto.objects.values_list("pk", flat=True)), [second.pk])

    def test_the_last_photograph_is_kept(self):
        """Deleting it would leave the sign-in page bare with no way back
        except uploading — refuse and say so."""
        only = self.seed()
        self.client.force_login(self.staff)
        response = self.client.post(
            reverse("backoffice:scene_delete", args=[only.pk]), secure=True, follow=True)
        self.assertTrue(LoginPhoto.objects.filter(pk=only.pk).exists())
        self.assertContains(response, "last photograph")

    # ---- the public backdrop ----
    def test_the_photograph_itself_is_public(self):
        """The sign-in page is unauthenticated, so its backdrop must be."""
        photo = self.seed()
        response = self.client.get(
            reverse("documents:login_photo", args=[photo.pk]), secure=True)
        self.assertEqual(response.status_code, 200)
        self.assertIn("public", response["Cache-Control"])

    def test_only_scene_photographs_are_reachable_that_way(self):
        response = self.client.get(reverse("documents:login_photo", args=[99999]), secure=True)
        self.assertEqual(response.status_code, 404)
