BareGit
from types import SimpleNamespace
import unittest
from unittest.mock import patch

from diffusion_cli.config import ImageGenerationRequest, UserConfig
from diffusion_cli.generation_service import GenerationService


class FakeComponent:
    load_count = 0
    to_device_count = 0
    to_cpu_count = 0

    def __init__(self, *_args, **_kwargs):
        type(self).load_count += 1

    def toDevice(self, *_args):
        type(self).to_device_count += 1

    def toCpu(self):
        type(self).to_cpu_count += 1


class FakeTextEncoder(FakeComponent):
    def encodePrompts(self, *_args):
        return "conditioning"


class FakeModel(FakeComponent):
    pass


class FakeVae(FakeComponent):
    def decode(self, latent):
        return latent


class GenerationServiceTest(unittest.TestCase):
    def setUp(self):
        for component in (FakeTextEncoder, FakeModel, FakeVae):
            component.load_count = 0
            component.to_device_count = 0
            component.to_cpu_count = 0

    def _runTwoRequests(self, model_residency):
        config = SimpleNamespace(
            prompt="a mug",
            negative_prompt="bad",
            seed=123,
            batch_size=1,
            height=64,
            width=64,
            steps=1,
            cfg=1.0,
            device="cuda",
            dtype="dtype",
            dtype_name="fp32",
            output="output.png",
            output_extension="webp",
            output_quality=80,
            tokenizer_path="tokenizer",
        )
        sources = SimpleNamespace(
            text_encoder="text",
            diffusion_model="diffusion",
            vae="vae",
        )
        request = ImageGenerationRequest(prompt="a mug")
        service = GenerationService(
            UserConfig(models=None, generation=None),
            model_residency=model_residency,
        )

        with (
            patch(
                "diffusion_cli.generation_service."
                "buildGenerationConfigFromRequest",
                return_value=config,
            ),
            patch(
                "diffusion_cli.generation_service."
                "resolveModelSourcesFromConfig",
                return_value=sources,
            ),
            patch(
                "diffusion_cli.generation_service.componentDtype",
                return_value="dtype",
            ),
            patch(
                "diffusion_cli.generation_service.ZImageTextEncoder",
                FakeTextEncoder,
            ),
            patch("diffusion_cli.generation_service.ZImageModel", FakeModel),
            patch("diffusion_cli.generation_service.ZImageVae", FakeVae),
            patch(
                "diffusion_cli.generation_service.sampleLatents",
                return_value="images",
            ),
            patch(
                "diffusion_cli.generation_service.encodeImages",
                return_value=[b"webp"],
            ) as encode_images,
        ):
            first = service.generateImages(request)
            service.generateImages(request)

        self.assertEqual(first[0].format, "webp")
        encode_images.assert_called_with("images", "webp", 80)

    def testCpuCacheLoadsComponentsOnceAcrossRequests(self):
        self._runTwoRequests("cpu-cache")

        self.assertEqual(FakeTextEncoder.load_count, 1)
        self.assertEqual(FakeModel.load_count, 1)
        self.assertEqual(FakeVae.load_count, 1)

    def testCpuCacheMovesComponentsForEachRequest(self):
        self._runTwoRequests("cpu-cache")

        self.assertEqual(FakeTextEncoder.to_device_count, 2)
        self.assertEqual(FakeModel.to_device_count, 2)
        self.assertEqual(FakeVae.to_device_count, 2)
        self.assertEqual(FakeTextEncoder.to_cpu_count, 2)
        self.assertEqual(FakeModel.to_cpu_count, 2)
        self.assertEqual(FakeVae.to_cpu_count, 2)

    def testStagedReloadsComponentsForEachRequest(self):
        self._runTwoRequests("staged")

        self.assertEqual(FakeTextEncoder.load_count, 2)
        self.assertEqual(FakeModel.load_count, 2)
        self.assertEqual(FakeVae.load_count, 2)

    def testGenerateToFilesPassesOutputFormatToImageIo(self):
        config = SimpleNamespace(
            output="output.png",
            output_extension="jpg",
            output_quality=85,
        )
        service = GenerationService(UserConfig(models=None, generation=None))

        with (
            patch(
                "diffusion_cli.generation_service."
                "buildGenerationConfigFromRequest",
                return_value=config,
            ),
            patch.object(service, "_generateTensor", return_value="images"),
            patch(
                "diffusion_cli.generation_service.saveImages",
                return_value=["output.jpg"],
            ) as save_images,
        ):
            paths = service.generateToFiles(ImageGenerationRequest("a mug"))

        self.assertEqual(paths, ["output.jpg"])
        save_images.assert_called_with("images", "output.png", "jpg", 85)


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