<?php

declare(strict_types=1);

namespace App\Tests\Unit\Service;

use App\Entity\Device;
use App\Entity\Image;
use App\Enum\DeviceModel;
use App\Enum\Orientation;
use App\Enum\RotationMode;
use App\Service\DeviceSerializer;
use PHPUnit\Framework\TestCase;

/**
 * The serializer is the wire-shape contract between REST and Mercure. A test
 * here doubles as a contract regression check: the SPA splats the same JSON
 * for both, so any silent rename/removal here would break live updates the
 * moment a device polls.
 */
class DeviceSerializerTest extends TestCase
{
    private DeviceSerializer $serializer;

    protected function setUp(): void
    {
        $this->serializer = new DeviceSerializer();
    }

    public function test_includes_all_expected_fields(): void
    {
        $device = $this->makeDevice();

        $payload = $this->serializer->serialize($device);

        $this->assertEqualsCanonicalizing(
            ['id', 'mac', 'name', 'model', 'orientation', 'rotationIntervalMinutes',
             'wakeTimes', 'timezone', 'uniquenessWindow', 'rotationMode',
             'prioritizeNeverShown', 'linkedAt', 'lastSeenAt',
             'nextPollExpectedAt', 'lockedImageId', 'currentImageId'],
            array_keys($payload),
        );
    }

    public function test_serializes_model_field(): void
    {
        $device = $this->makeDevice();
        $device->setModel(\App\Enum\DeviceModel::V2);
        $payload = $this->serializer->serialize($device);
        $this->assertSame('v2', $payload['model']);
    }

    public function test_serializes_scalars_in_expected_shapes(): void
    {
        $device = $this->makeDevice();
        $device->setName('Living Room');
        $device->setOrientation(Orientation::Portrait);
        $device->setRotationIntervalMinutes(15);
        $device->setWakeTimes([6 * 60, 18 * 60]);
        $device->setTimezone('America/Chicago');
        $device->setUniquenessWindow(7);
        $device->setRotationMode(RotationMode::Random);
        $device->setPrioritizeNeverShown(true);

        $payload = $this->serializer->serialize($device);

        $this->assertSame('Living Room', $payload['name']);
        $this->assertSame('portrait', $payload['orientation']);
        $this->assertSame(15, $payload['rotationIntervalMinutes']);
        $this->assertSame([360, 1080], $payload['wakeTimes']);
        $this->assertSame('America/Chicago', $payload['timezone']);
        $this->assertSame(7, $payload['uniquenessWindow']);
        $this->assertSame('random', $payload['rotationMode']);
        $this->assertTrue($payload['prioritizeNeverShown']);
    }

    public function test_serializes_nullable_timestamps_as_null_when_unset(): void
    {
        $device = $this->makeDevice();

        $payload = $this->serializer->serialize($device);

        $this->assertNull($payload['lastSeenAt']);
        $this->assertNull($payload['nextPollExpectedAt']);
        $this->assertNull($payload['lockedImageId']);
        $this->assertNull($payload['currentImageId']);
    }

    public function test_serializes_timestamps_as_iso_8601(): void
    {
        $device = $this->makeDevice();
        $device->markSeen();
        $device->setNextPollExpectedAt(new \DateTimeImmutable('2026-05-07T12:34:56+00:00'));

        $payload = $this->serializer->serialize($device);

        $this->assertMatchesRegularExpression(
            '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/',
            $payload['lastSeenAt'],
        );
        $this->assertSame('2026-05-07T12:34:56+00:00', $payload['nextPollExpectedAt']);
    }

    public function test_lockedImageId_and_currentImageId_reflect_assignments(): void
    {
        $device = $this->makeDevice();

        $image = $this->makeImage(42);
        $device->setLockedImage($image);
        $device->setCurrentImage($image);

        $payload = $this->serializer->serialize($device);

        $this->assertSame(42, $payload['lockedImageId']);
        $this->assertSame(42, $payload['currentImageId']);
    }

    private function makeDevice(): Device
    {
        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:FF');
        $device->setName('Test');
        $device->setModel(DeviceModel::V1);
        $device->setOrientation(Orientation::Landscape);
        // Persisted-id is null in unit-tests; serializer must tolerate it.
        return $device;
    }

    private function makeImage(int $id): Image
    {
        $image = new Image();
        // Image::$id is set by Doctrine — bypass via reflection in unit context.
        $ref = new \ReflectionProperty(Image::class, 'id');
        $ref->setAccessible(true);
        $ref->setValue($image, $id);
        return $image;
    }
}
