<?php

declare(strict_types=1);

namespace App\Tests\Integration\MessageHandler;

use App\Entity\Device;
use App\Entity\Image;
use App\Message\RunImageCleanupMessage;
use App\MessageHandler\RunImageCleanupMessageHandler;
use App\Repository\ImageRepository;
use App\Tests\AppKernelTestCase;
use Doctrine\ORM\EntityManagerInterface;

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

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

    protected function tearDown(): void
    {
        foreach ($this->createdDirs as $dir) {
            if (is_dir($dir)) {
                $this->deleteDir($dir);
            }
        }
        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(): void
    {
        $handler = static::getContainer()->get(RunImageCleanupMessageHandler::class);
        $handler(new RunImageCleanupMessage());
    }

    private function makeImage(string $email): Image
    {
        $user  = $this->createUser($email);
        $image = (new Image())->setUser($user)
            ->setOriginalFilename('del.jpg')
            ->setStoragePath('var/storage/images/_cleanup_test.jpg');
        $this->em()->persist($image);
        $this->em()->flush();
        return $image;
    }

    // CL-01: soft-deleted image with no approvals → hard-deleted from DB and filesystem
    public function test_cl01_soft_deleted_no_approvals_is_hard_deleted(): void
    {
        $image = $this->makeImage('cl01@example.com');
        $image->setDeletedAt(new \DateTimeImmutable('-1 day'));
        $this->em()->flush();

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

        // Add a file and a subdirectory to cover unlink + recursive deleteDirectory branches
        file_put_contents($dir . '/v1_landscape.bin', 'FAKEBIN');
        $subdir = $dir . '/subdir';
        mkdir($subdir, 0755, true);
        file_put_contents($subdir . '/nested.bin', 'NESTEDBIN');

        $this->invokeHandler();

        $this->em()->clear();
        $found = $this->em()->find(Image::class, $imageId);
        $this->assertNull($found, 'Image should be hard-deleted from the database');
        $this->assertFalse(is_dir($dir), 'Image directory should be removed from filesystem');

        $this->createdDirs = array_filter($this->createdDirs, fn($d) => $d !== $dir);
    }

    // CL-02: soft-deleted image with approved device → skipped, stays in DB
    public function test_cl02_soft_deleted_with_approval_is_skipped(): void
    {
        $image = $this->makeImage('cl02@example.com');

        $device = new Device();
        $device->setMac('CC:DD:EE:FF:00:01');
        $device->setName('Frame');
        $device->setUser($image->getUser());
        $this->em()->persist($device);
        $image->approveForDevice($device);
        $image->setDeletedAt(new \DateTimeImmutable('-1 day'));
        $this->em()->flush();

        $imageId = $image->getId();

        $this->invokeHandler();

        $this->em()->clear();
        $found = $this->em()->find(Image::class, $imageId);
        $this->assertNotNull($found, 'Image with active approvals should not be hard-deleted');
    }

    // CL-03: non-deleted image → not touched
    public function test_cl03_non_deleted_image_is_not_touched(): void
    {
        $image   = $this->makeImage('cl03@example.com');
        $imageId = $image->getId();

        $this->invokeHandler();

        $this->em()->clear();
        $found = $this->em()->find(Image::class, $imageId);
        $this->assertNotNull($found, 'Non-deleted image should not be affected');
    }

    // CL-04: error during cleanup is caught and logged; no exception propagates
    public function test_cl04_error_during_cleanup_is_caught(): void
    {
        $image = $this->makeImage('cl04@example.com');
        $image->setDeletedAt(new \DateTimeImmutable('-1 day'));
        $this->em()->flush();

        $mockRepo = $this->createStub(ImageRepository::class);
        $mockRepo->method('findSoftDeleted')->willReturn([$image]);

        $mockEm = $this->createStub(EntityManagerInterface::class);
        $mockEm->method('remove')->willThrowException(new \RuntimeException('simulated error'));

        $logger = static::getContainer()->get(\Psr\Log\LoggerInterface::class);

        $handler = new RunImageCleanupMessageHandler(
            $mockRepo,
            $mockEm,
            $logger,
            $this->projectDir,
        );

        $handler(new RunImageCleanupMessage());
        $this->assertTrue(true, 'catch block executed — no exception propagated');
    }
}
