<?php

declare(strict_types=1);

namespace App\Tests\Unit\Service;

use App\Entity\Device;
use App\Entity\DeviceImageHistory;
use App\Entity\Image;
use App\Entity\RenderedAsset;
use App\Enum\DeviceModel;
use App\Enum\Orientation;
use App\Enum\RenderStatus;
use App\Service\RotationService;
use App\Tests\AppKernelTestCase;

/**
 * Integration tests for RotationService (requires real DB + dama rollback).
 */
class RotationServiceTest extends AppKernelTestCase
{
    private RotationService $rotation;

    protected function setUp(): void
    {
        parent::setUp();
        $this->rotation = static::getContainer()->get(RotationService::class);
    }

    /**
     * Creates a User+Device approved for a ready Image with V1/Landscape asset.
     * Returns [Device, Image].
     */
    private function createDeviceWithReadyImage(
        string $email = 'test@example.com',
        RenderStatus $status = RenderStatus::Ready,
        bool $approveForDevice = true,
        ?\DateTimeImmutable $uploadedAt = null,
    ): array {
        $user = $this->createUser($email);

        $device = new Device();
        $device->setMac(strtoupper(bin2hex(random_bytes(3)) . ':' . bin2hex(random_bytes(3)) . ':AA'));
        $device->setName('Test Device');
        $device->setUser($user);
        $device->setModel(DeviceModel::V1);
        $device->setOrientation(Orientation::Landscape);
        self::em()->persist($device);

        $image = new Image();
        $image->setUser($user);
        $image->setOriginalFilename('test.jpg');
        $image->setStoragePath('var/storage/images/test/original.jpg');
        if ($uploadedAt !== null) {
            // Use reflection to set uploadedAt since it's set in constructor
            $ref = new \ReflectionProperty(Image::class, 'uploadedAt');
            $ref->setAccessible(true);
            $ref->setValue($image, $uploadedAt);
        }
        if ($approveForDevice) {
            $image->approveForDevice($device);
        }
        self::em()->persist($image);

        $asset = (new RenderedAsset())
            ->setImage($image)
            ->setDeviceModel(DeviceModel::V1)
            ->setOrientation(Orientation::Landscape)
            ->setStatus($status)
            ->setFilePath('var/storage/images/test/v1_landscape.bin');
        self::em()->persist($asset);

        self::em()->flush();

        return [$device, $image];
    }

    public function test_advance_returns_null_when_pool_empty(): void
    {
        $user = $this->createUser('empty@example.com');
        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:FF');
        $device->setName('Empty Device');
        $device->setUser($user);
        self::em()->persist($device);
        self::em()->flush();

        $result = $this->rotation->advance($device);

        $this->assertNull($result);
    }

    public function test_advance_picks_oldest_image(): void
    {
        $user = $this->createUser('oldest@example.com');

        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:01');
        $device->setName('Device');
        $device->setUser($user);
        self::em()->persist($device);

        $older = new Image();
        $older->setUser($user)->setOriginalFilename('old.jpg')->setStoragePath('x');
        $ref = new \ReflectionProperty(Image::class, 'uploadedAt');
        $ref->setAccessible(true);
        $ref->setValue($older, new \DateTimeImmutable('2024-01-01'));
        $older->approveForDevice($device);
        self::em()->persist($older);

        $assetOld = (new RenderedAsset())->setImage($older)->setDeviceModel(DeviceModel::V1)
            ->setOrientation(Orientation::Landscape)->setStatus(RenderStatus::Ready)->setFilePath('x/old.bin');
        self::em()->persist($assetOld);

        $newer = new Image();
        $newer->setUser($user)->setOriginalFilename('new.jpg')->setStoragePath('y');
        $ref->setValue($newer, new \DateTimeImmutable('2024-06-01'));
        $newer->approveForDevice($device);
        self::em()->persist($newer);

        $assetNew = (new RenderedAsset())->setImage($newer)->setDeviceModel(DeviceModel::V1)
            ->setOrientation(Orientation::Landscape)->setStatus(RenderStatus::Ready)->setFilePath('y/new.bin');
        self::em()->persist($assetNew);

        self::em()->flush();

        $result = $this->rotation->advance($device);

        $this->assertNotNull($result);
        $this->assertSame($older->getId(), $result->getId());
    }

    public function test_advance_skips_recently_shown(): void
    {
        [$device, $imageA] = $this->createDeviceWithReadyImage('skip@example.com');

        $imageB = new Image();
        $imageB->setUser($device->getUser())->setOriginalFilename('b.jpg')->setStoragePath('b');
        $imageB->approveForDevice($device);
        self::em()->persist($imageB);

        $assetB = (new RenderedAsset())->setImage($imageB)->setDeviceModel(DeviceModel::V1)
            ->setOrientation(Orientation::Landscape)->setStatus(RenderStatus::Ready)->setFilePath('b.bin');
        self::em()->persist($assetB);

        // Put imageA in history
        $history = new DeviceImageHistory($device, $imageA);
        self::em()->persist($history);

        $device->setUniquenessWindow(2);
        self::em()->flush();

        $result = $this->rotation->advance($device);

        $this->assertNotNull($result);
        $this->assertSame($imageB->getId(), $result->getId());
    }

    public function test_advance_resets_when_all_in_window(): void
    {
        [$device, $imageA] = $this->createDeviceWithReadyImage('reset@example.com');

        $imageB = new Image();
        $imageB->setUser($device->getUser())->setOriginalFilename('b.jpg')->setStoragePath('b');
        $imageB->approveForDevice($device);
        self::em()->persist($imageB);

        $assetB = (new RenderedAsset())->setImage($imageB)->setDeviceModel(DeviceModel::V1)
            ->setOrientation(Orientation::Landscape)->setStatus(RenderStatus::Ready)->setFilePath('b.bin');
        self::em()->persist($assetB);

        $histA = new DeviceImageHistory($device, $imageA);
        $histB = new DeviceImageHistory($device, $imageB);
        self::em()->persist($histA);
        self::em()->persist($histB);

        $device->setUniquenessWindow(2);
        self::em()->flush();

        $result = $this->rotation->advance($device);

        $this->assertNotNull($result);
    }

    public function test_advance_respects_uniqueness_window(): void
    {
        $user = $this->createUser('window@example.com');

        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:02');
        $device->setName('Window Device');
        $device->setUser($user);
        $device->setUniquenessWindow(1);
        self::em()->persist($device);

        $images = [];
        for ($i = 0; $i < 3; $i++) {
            $img = new Image();
            $img->setUser($user)->setOriginalFilename("img{$i}.jpg")->setStoragePath("p{$i}");
            $ref = new \ReflectionProperty(Image::class, 'uploadedAt');
            $ref->setAccessible(true);
            $ref->setValue($img, new \DateTimeImmutable("2024-01-0" . ($i + 1)));
            $img->approveForDevice($device);
            self::em()->persist($img);
            $asset = (new RenderedAsset())->setImage($img)->setDeviceModel(DeviceModel::V1)
                ->setOrientation(Orientation::Landscape)->setStatus(RenderStatus::Ready)->setFilePath("p{$i}.bin");
            self::em()->persist($asset);
            $images[] = $img;
        }

        // Put image[0] in history (most recent)
        $hist = new DeviceImageHistory($device, $images[0]);
        self::em()->persist($hist);
        self::em()->flush();

        $result = $this->rotation->advance($device);

        $this->assertNotNull($result);
        $this->assertNotSame($images[0]->getId(), $result->getId());
    }

    public function test_advance_persists_history(): void
    {
        [$device] = $this->createDeviceWithReadyImage('history@example.com');

        $this->rotation->advance($device);

        $count = self::em()->createQueryBuilder()
            ->select('COUNT(h.id)')
            ->from(DeviceImageHistory::class, 'h')
            ->where('h.device = :device')
            ->setParameter('device', $device)
            ->getQuery()
            ->getSingleScalarResult();

        $this->assertSame(1, (int) $count);
    }

    public function test_advance_updates_current_image(): void
    {
        [$device, $image] = $this->createDeviceWithReadyImage('current@example.com');

        $this->rotation->advance($device);

        self::em()->refresh($device);

        $this->assertNotNull($device->getCurrentImage());
        $this->assertSame($image->getId(), $device->getCurrentImage()->getId());
    }

    public function test_advance_excludes_pending_asset(): void
    {
        [$device, ] = $this->createDeviceWithReadyImage('pending@example.com', RenderStatus::Pending);

        $result = $this->rotation->advance($device);

        $this->assertNull($result);
    }

    public function test_advance_excludes_unapproved_image(): void
    {
        [$device, ] = $this->createDeviceWithReadyImage('unapproved@example.com', RenderStatus::Ready, false);

        $result = $this->rotation->advance($device);

        $this->assertNull($result);
    }
}
