"""Shared image validation: extension + size + genuine image content (Pillow).

`image_validation_error` is the single shared validator used by advertisement
image uploads and the Premium QR upload. These tests exercise it directly with
real and fake images, and confirm both entry points (`image_validation_error`
and the premium `qr_validation_error` that delegates to it) reject non-images.
"""

import io
import os
import unittest

os.environ["TEST_DATABASE_URL"] = "sqlite:///:memory:"

from werkzeug.datastructures import FileStorage

from app import create_app
from app.admin.advertisements.services import image_validation_error
from app.premium.config import qr_validation_error


def _image_bytes(fmt="PNG"):
    from PIL import Image
    buf = io.BytesIO()
    Image.new("RGB", (16, 16), "blue").save(buf, fmt)
    return buf.getvalue()


def _fs(name, data):
    return FileStorage(stream=io.BytesIO(data), filename=name)


class ImageValidationTests(unittest.TestCase):
    def setUp(self):
        self.app = create_app("testing")
        self.ctx = self.app.app_context()
        self.ctx.push()

    def tearDown(self):
        self.ctx.pop()

    # --- valid images pass -----------------------------------------------------
    def test_valid_png(self):
        self.assertIsNone(image_validation_error(_fs("a.png", _image_bytes("PNG"))))

    def test_valid_jpeg(self):
        self.assertIsNone(image_validation_error(_fs("a.jpg", _image_bytes("JPEG"))))

    def test_valid_webp(self):
        self.assertIsNone(image_validation_error(_fs("a.webp", _image_bytes("WEBP"))))

    def test_valid_image_stream_rewound_for_save(self):
        # After validation the stream must be at 0 so the save helper reads it all.
        fs = _fs("a.png", _image_bytes("PNG"))
        self.assertIsNone(image_validation_error(fs))
        self.assertEqual(fs.stream.tell(), 0)
        self.assertTrue(fs.read())  # non-empty ⇒ still readable from the start

    # --- content verification rejects fakes ------------------------------------
    def test_random_text_renamed_png(self):
        msg = image_validation_error(_fs("evil.png", b"this is just text, not an image"))
        self.assertEqual(msg, "The uploaded file is not a valid image.")

    def test_random_binary_renamed_jpg(self):
        msg = image_validation_error(_fs("evil.jpg", os.urandom(512)))
        self.assertEqual(msg, "The uploaded file is not a valid image.")

    def test_corrupted_png_header_only(self):
        msg = image_validation_error(_fs("bad.png", b"\x89PNG\r\n\x1a\n" + b"\x00" * 64))
        self.assertEqual(msg, "The uploaded file is not a valid image.")

    def test_corrupted_jpeg(self):
        msg = image_validation_error(_fs("bad.jpg", b"\xff\xd8\xff\xe0" + b"\x00" * 64))
        self.assertEqual(msg, "The uploaded file is not a valid image.")

    def test_truncated_image(self):
        raw = _image_bytes("PNG")
        truncated = raw[: len(raw) // 2]
        self.assertEqual(image_validation_error(_fs("cut.png", truncated)),
                         "The uploaded file is not a valid image.")

    # --- extension + size still enforced (order: ext → size → content) ---------
    def test_extension_still_rejected_first(self):
        self.assertIn("Unsupported image type",
                      image_validation_error(_fs("notes.txt", b"whatever")))

    def test_size_still_rejected_before_content(self):
        self.app.config["MAX_AD_IMAGE_SIZE"] = 1024
        # Oversized non-image: rejected on size, never reaches content verify.
        self.assertIn("too large", image_validation_error(_fs("big.png", b"\x00" * 4096)))

    # --- single shared implementation (ad + QR) --------------------------------
    def test_qr_validator_delegates_to_shared(self):
        # Premium QR validation goes through the same helper.
        self.assertIsNone(qr_validation_error(_fs("qr.png", _image_bytes("PNG"))))
        self.assertEqual(qr_validation_error(_fs("qr.png", b"not an image")),
                         "The uploaded file is not a valid image.")


if __name__ == "__main__":
    unittest.main()
