<?php

declare(strict_types=1);

namespace App\Tests\Integration\MessageHandler;

use App\Entity\Image;
use App\Enum\RenderStatus;
use App\Message\RenderImageMessage;
use App\MessageHandler\RenderImageMessageHandler;
use App\Repository\RenderedAssetRepository;
use App\Tests\AppKernelTestCase;

class RenderImageMessageHandlerTest extends AppKernelTestCase
{
    private string $projectDir;
    private string $fixtureJpeg;
    private array $createdDirs = [];

    protected function setUp(): void
    {
        parent::setUp();
        $this->projectDir  = static::getContainer()->getParameter('kernel.project_dir');
        $this->createdDirs = [];

        $storageDir = $this->projectDir . '/var/storage/images';
        if (!is_dir($storageDir)) {
            mkdir($storageDir, 0755, true);
        }

        $this->fixtureJpeg = $this->projectDir . '/var/storage/images/_render_fixture.jpg';
        $imagick = new \Imagick();
        $imagick->newImage(20, 20, new \ImagickPixel('white'));
        $imagick->setImageFormat('jpeg');
        $imagick->writeImage($this->fixtureJpeg);
        $imagick->destroy();
    }

    protected function tearDown(): void
    {
        foreach ($this->createdDirs as $dir) {
            if (is_dir($dir)) {
                $this->deleteDir($dir);
            }
        }
        if (file_exists($this->fixtureJpeg)) {
            unlink($this->fixtureJpeg);
        }
        parent::tearDown();
    }

    private function deleteDir(string $dir): void
    {
        foreach (new \FilesystemIterator($dir) as $item) {
            $item->isDir() ? $this->deleteDir($item->getPathname()) : unlink($item->getPathname());
        }
        rmdir($dir);
    }

    private function invokeHandler(int $imageId, string $model = 'v1', string $orientation = 'landscape'): void
    {
        $handler = static::getContainer()->get(RenderImageMessageHandler::class);
        $handler(new RenderImageMessage($imageId, $model, $orientation));
    }

    // MH-01: happy path — bin written, RenderedAsset status = Ready
    public function test_mh01_renders_image_to_bin_and_marks_ready(): void
    {
        $user  = $this->createUser('mh01@example.com');
        $image = (new Image())->setUser($user)
            ->setOriginalFilename('test.jpg')
            ->setStoragePath('var/storage/images/_render_fixture.jpg');
        $this->em()->persist($image);
        $this->em()->flush();

        $imageId  = $image->getId();
        $imageDir = $this->projectDir . '/var/storage/images/' . $imageId;
        mkdir($imageDir, 0755, true);
        $this->createdDirs[] = $imageDir;

        $this->invokeHandler($imageId);

        /** @var RenderedAssetRepository $assetRepo */
        $assetRepo = $this->em()->getRepository(\App\Entity\RenderedAsset::class);
        $asset     = $assetRepo->findOneBy(['image' => $image]);

        $this->assertNotNull($asset);
        $this->assertSame(RenderStatus::Ready, $asset->getStatus());
        $this->assertFileExists($this->projectDir . '/' . $asset->getFilePath());
    }

    // MH-01b: composited.jpg is preferred over the raw original when present
    public function test_mh01b_composited_jpg_preferred_over_original(): void
    {
        $user  = $this->createUser('mh01b@example.com');
        $image = (new Image())->setUser($user)
            ->setOriginalFilename('test.jpg')
            ->setStoragePath('var/storage/images/_render_fixture.jpg');
        $this->em()->persist($image);
        $this->em()->flush();

        $imageId  = $image->getId();
        $imageDir = $this->projectDir . '/var/storage/images/' . $imageId;
        mkdir($imageDir, 0755, true);
        $this->createdDirs[] = $imageDir;

        copy($this->fixtureJpeg, $imageDir . '/composited.jpg');

        $this->invokeHandler($imageId);

        $assetRepo = $this->em()->getRepository(\App\Entity\RenderedAsset::class);
        $asset     = $assetRepo->findOneBy(['image' => $image]);

        $this->assertNotNull($asset);
        $this->assertSame(RenderStatus::Ready, $asset->getStatus());
    }

    // MH-02: non-existent imageId → handler returns early, no RenderedAsset created
    public function test_mh02_nonexistent_image_returns_early(): void
    {
        $this->invokeHandler(999999999);

        $assetRepo = $this->em()->getRepository(\App\Entity\RenderedAsset::class);
        $assets    = $assetRepo->findAll();
        $this->assertCount(0, $assets);
    }

    // MH-03: corrupt/non-image file → RenderedAsset status = Failed
    public function test_mh03_imagick_failure_marks_asset_as_failed(): void
    {
        $badFile = $this->projectDir . '/var/storage/images/_render_bad.txt';
        file_put_contents($badFile, 'not-an-image');

        $user  = $this->createUser('mh03@example.com');
        $image = (new Image())->setUser($user)
            ->setOriginalFilename('bad.jpg')
            ->setStoragePath('var/storage/images/_render_bad.txt');
        $this->em()->persist($image);
        $this->em()->flush();

        $imageId  = $image->getId();
        $imageDir = $this->projectDir . '/var/storage/images/' . $imageId;
        mkdir($imageDir, 0755, true);
        $this->createdDirs[] = $imageDir;

        $this->invokeHandler($imageId);

        $assetRepo = $this->em()->getRepository(\App\Entity\RenderedAsset::class);
        $asset     = $assetRepo->findOneBy(['image' => $image]);

        $this->assertNotNull($asset);
        $this->assertSame(RenderStatus::Failed, $asset->getStatus());

        if (file_exists($badFile)) {
            unlink($badFile);
        }
    }

    // MH-PORTRAIT: portrait orientation rotates the source 90° CCW so the
    // photo's top edge maps to the EPD's left column (the panel is physically
    // rotated when mounted vertically). Without the rotate, the rendered .bin
    // would come out sideways. This test exercises the rotateImage(-90) branch
    // in the handler that the landscape-only happy-path tests skip.
    public function test_portrait_rotation_branch_runs(): void
    {
        $user  = $this->createUser('mh-portrait@example.com');
        $image = (new Image())->setUser($user)
            ->setOriginalFilename('p.jpg')
            ->setStoragePath('var/storage/images/_render_fixture.jpg');
        $this->em()->persist($image);
        $this->em()->flush();

        $imageId  = $image->getId();
        $imageDir = $this->projectDir . '/var/storage/images/' . $imageId;
        mkdir($imageDir, 0755, true);
        $this->createdDirs[] = $imageDir;

        $this->invokeHandler($imageId, 'v1', 'portrait');

        // Portrait .bin should still be 800×480 EPD-native (192,000 bytes).
        $binPath = $imageDir . '/v1_portrait.bin';
        $this->assertFileExists($binPath);
        $this->assertSame(192000, filesize($binPath));

        $assetRepo = static::getContainer()->get(\App\Repository\RenderedAssetRepository::class);
        $asset = $assetRepo->findOneBy(['image' => $image, 'orientation' => \App\Enum\Orientation::Portrait]);
        $this->assertNotNull($asset);
        $this->assertSame(RenderStatus::Ready, $asset->getStatus());
    }
}
