BareGit
import tempfile
import unittest
from subprocess import CalledProcessError
from pathlib import Path
from unittest.mock import patch

from PIL import Image
import torch

from diffusion_cli.errors import DiffusionCliError
from diffusion_cli.image_io import (
    convertPngFile,
    encodeImages,
    outputPathWithExtension,
    outputPaths,
    saveImages,
)


class ImageIoTest(unittest.TestCase):
    def testOutputPathsAppendBatchIndexBeforeSuffix(self):
        paths = outputPaths(Path("output.png"), 2, "jpg")

        self.assertEqual(paths, [Path("output_0000.jpg"),
                                 Path("output_0001.jpg")])

    def testOutputPathWithExtensionReplacesOrAddsSuffix(self):
        self.assertEqual(
            outputPathWithExtension(Path("result.png"), "webp"),
            Path("result.webp"),
        )
        self.assertEqual(
            outputPathWithExtension(Path("result"), "jpg"),
            Path("result.jpg"),
        )

    def testSaveImagesWritesPngWithExpectedSize(self):
        images = torch.zeros(1, 3, 4, 5)

        with tempfile.TemporaryDirectory() as temp_dir:
            output = Path(temp_dir) / "image.png"
            paths = saveImages(images, output)
            with Image.open(paths[0]) as image:
                size = image.size
                mode = image.mode

        self.assertEqual(paths, [output])
        self.assertEqual(size, (5, 4))
        self.assertEqual(mode, "RGB")

    def testSaveImagesUsesFinalSuffixForNonPngOutput(self):
        images = torch.zeros(1, 3, 4, 5)

        with tempfile.TemporaryDirectory() as temp_dir:
            output = Path(temp_dir) / "image.png"

            def fake_run(args, **_kwargs):
                Path(args[-1]).write_bytes(b"jpg")

            with patch("diffusion_cli.image_io.subprocess.run", fake_run):
                paths = saveImages(images, output, "jpg", 85)

        self.assertEqual(paths, [Path(temp_dir) / "image.jpg"])

    def testConvertPngFileUsesImageMagickArgumentList(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            input_path = root / "input.png"
            output_path = root / "output.webp"
            input_path.write_bytes(b"png")

            def fake_run(args, **kwargs):
                self.assertEqual(
                    args,
                    [
                        "magick",
                        str(input_path),
                        "-quality",
                        "80",
                        str(output_path),
                    ],
                )
                self.assertTrue(kwargs["check"])
                self.assertTrue(kwargs["capture_output"])
                self.assertTrue(kwargs["text"])
                output_path.write_bytes(b"webp")

            with patch("diffusion_cli.image_io.subprocess.run", fake_run):
                convertPngFile(input_path, output_path, "webp", 80)

    def testConvertPngFileReportsMissingImageMagick(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            input_path = Path(temp_dir) / "input.png"
            output_path = Path(temp_dir) / "output.avif"
            input_path.write_bytes(b"png")

            with patch(
                "diffusion_cli.image_io.subprocess.run",
                side_effect=FileNotFoundError(),
            ):
                with self.assertRaises(DiffusionCliError) as context:
                    convertPngFile(input_path, output_path, "avif", 95)

        self.assertIn(
            "ImageMagick executable not found",
            str(context.exception),
        )

    def testConvertPngFileReportsImageMagickFailureStderr(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            input_path = Path(temp_dir) / "input.png"
            output_path = Path(temp_dir) / "output.avif"
            input_path.write_bytes(b"png")
            error = CalledProcessError(
                1,
                ["magick"],
                stderr="no encode delegate\n",
            )

            with patch(
                "diffusion_cli.image_io.subprocess.run",
                side_effect=error,
            ):
                with self.assertRaises(DiffusionCliError) as context:
                    convertPngFile(input_path, output_path, "avif", 95)

        self.assertIn(
            "ImageMagick failed to write avif output: no encode delegate",
            str(context.exception),
        )

    def testEncodeImagesReturnsPngBytes(self):
        images = torch.zeros(1, 3, 4, 5)

        encoded = encodeImages(images)

        self.assertEqual(len(encoded), 1)
        self.assertTrue(encoded[0].startswith(b"\x89PNG\r\n\x1a\n"))

    def testEncodeImagesReturnsConvertedBytesForNonPng(self):
        images = torch.zeros(1, 3, 4, 5)

        def fake_run(args, **_kwargs):
            Path(args[-1]).write_bytes(b"converted")

        with patch("diffusion_cli.image_io.subprocess.run", fake_run):
            encoded = encodeImages(images, "webp", 90)

        self.assertEqual(encoded, [b"converted"])


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