import io
import zipfile

from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client, TestCase, override_settings
from django.urls import reverse
from PIL import Image

from apps.pages.contact_uploads import (
    MAX_ATTACHMENTS,
    MAX_FILE_SIZE,
    validate_contact_attachments,
)
from apps.pages.models import ContactSubmission, ContactSubmissionAttachment


def _png_file(name="screenshot.png"):
    buffer = io.BytesIO()
    Image.new("RGB", (8, 8), color="red").save(buffer, format="PNG")
    buffer.seek(0)
    return SimpleUploadedFile(name, buffer.read(), content_type="image/png")


def _zip_file(name="logs.zip", entries=None):
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive:
        for entry_name, content in (entries or {"app.log": "line one\n"}).items():
            archive.writestr(entry_name, content)
    buffer.seek(0)
    return SimpleUploadedFile(name, buffer.read(), content_type="application/zip")


class ContactUploadValidationTest(TestCase):
    def test_accepts_valid_png(self):
        validated = validate_contact_attachments([_png_file()])
        self.assertEqual(len(validated), 1)
        self.assertEqual(validated[0][1], "screenshot.png")

    def test_accepts_valid_zip(self):
        validated = validate_contact_attachments([_zip_file()])
        self.assertEqual(len(validated), 1)

    def test_accepts_valid_text_log(self):
        uploaded = SimpleUploadedFile(
            "error.log",
            b"2026-06-07 ERROR something failed\n",
            content_type="text/plain",
        )
        validated = validate_contact_attachments([uploaded])
        self.assertEqual(validated[0][1], "error.log")

    def test_rejects_executable_extension(self):
        uploaded = SimpleUploadedFile(
            "malware.exe",
            b"MZfake",
            content_type="application/octet-stream",
        )
        with self.assertRaises(Exception):
            validate_contact_attachments([uploaded])

    def test_rejects_php_disguised_as_png(self):
        uploaded = SimpleUploadedFile(
            "image.png",
            b"<?php echo 'bad'; ?>",
            content_type="image/png",
        )
        with self.assertRaises(Exception):
            validate_contact_attachments([uploaded])

    def test_rejects_oversized_file(self):
        uploaded = SimpleUploadedFile(
            "big.log",
            b"x" * (MAX_FILE_SIZE + 1),
            content_type="text/plain",
        )
        with self.assertRaises(Exception):
            validate_contact_attachments([uploaded])

    def test_rejects_too_many_files(self):
        files = [_png_file(f"shot-{index}.png") for index in range(MAX_ATTACHMENTS + 1)]
        with self.assertRaises(Exception):
            validate_contact_attachments(files)

    def test_rejects_zip_with_path_traversal(self):
        buffer = io.BytesIO()
        with zipfile.ZipFile(buffer, "w") as archive:
            archive.writestr("../escape.txt", "bad")
        buffer.seek(0)
        uploaded = SimpleUploadedFile(
            "bad.zip",
            buffer.read(),
            content_type="application/zip",
        )
        with self.assertRaises(Exception):
            validate_contact_attachments([uploaded])


@override_settings(
    CONTACT_UPLOAD_ROOT=__import__("pathlib").Path(__file__).resolve().parents[3]
    / "test_private_uploads"
)
class ContactViewUploadTest(TestCase):
    def setUp(self):
        self.client = Client(enforce_csrf_checks=True)
        self.url = reverse("pages:contact")

    def _start_session(self):
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        self.csrf_token = response.cookies["csrftoken"].value
        captcha_question = response.context["captcha_question"]
        left, right = captcha_question.split(" + ")
        return int(left) + int(right)

    def _post_contact(self, captcha_answer, attachments=None, extra=None):
        payload = {
            "name": "Test User",
            "title": "Upload test",
            "description": "Testing attachments",
            "email": "test@example.com",
            "captcha_answer": captcha_answer,
        }
        if attachments is not None:
            payload["attachments"] = attachments
        if extra:
            payload.update(extra)
        return self.client.post(
            self.url,
            data=payload,
            HTTP_X_CSRFTOKEN=self.csrf_token,
        )

    def test_contact_submission_with_png_attachment(self):
        captcha_answer = self._start_session()
        response = self._post_contact(captcha_answer, attachments=_png_file())
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.json()["success"])
        submission = ContactSubmission.objects.get(title="Upload test")
        self.assertEqual(submission.attachments.count(), 1)
        attachment = submission.attachments.first()
        self.assertEqual(attachment.original_filename, "screenshot.png")
        self.assertTrue(attachment.file.storage.exists(attachment.file.name))

    def test_contact_submission_rejects_invalid_attachment(self):
        captcha_answer = self._start_session()
        response = self._post_contact(
            captcha_answer,
            attachments=SimpleUploadedFile(
                "bad.exe",
                b"MZ",
                content_type="application/octet-stream",
            ),
        )
        self.assertEqual(response.status_code, 400)
        self.assertFalse(response.json()["success"])
        self.assertIn("attachments", response.json()["errors"])
        self.assertEqual(ContactSubmission.objects.count(), 0)
        self.assertEqual(ContactSubmissionAttachment.objects.count(), 0)
