<?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\DeviceService;
use App\Tests\AppKernelTestCase;

class DeviceServiceTest extends AppKernelTestCase
{
    private DeviceService $service;

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

    public function test_link_creates_new_device_with_correct_mac_and_user(): void
    {
        $user   = $this->createUser('link1@example.com');
        $device = $this->service->linkToUser('aa:bb:cc:dd:ee:01', $user);

        $this->assertNotNull($device->getId());
        $this->assertSame('AA:BB:CC:DD:EE:01', $device->getMac());
        $this->assertSame($user->getId(), $device->getUser()->getId());
    }

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

        $d1 = $this->service->linkToUser('aa:bb:cc:dd:ee:02', $user);
        $d2 = $this->service->linkToUser('aa:bb:cc:dd:ee:02', $user);

        $this->assertSame($d1->getId(), $d2->getId());
        $this->assertSame($user->getId(), $d2->getUser()->getId());
    }

    public function test_link_purges_history_on_ownership_transfer(): void
    {
        $user1 = $this->createUser('owner1@example.com');
        $user2 = $this->createUser('owner2@example.com');

        // Create device owned by user1 with an image and history
        $device = $this->service->linkToUser('aa:bb:cc:dd:ee:03', $user1);

        $image = (new Image())->setUser($user1)->setOriginalFilename('x.jpg')->setStoragePath('x');
        $image->approveForDevice($device);
        $this->em()->persist($image);

        $history = new DeviceImageHistory($device, $image);
        $this->em()->persist($history);
        $this->em()->flush();

        // Transfer to user2 — must explicitly opt in via allowClaim now,
        // otherwise the service throws DeviceClaimRequiredException.
        $this->service->linkToUser('aa:bb:cc:dd:ee:03', $user2, allowClaim: true);

        // History should be gone
        $count = $this->em()->createQueryBuilder()
            ->select('COUNT(h.id)')
            ->from(DeviceImageHistory::class, 'h')
            ->where('h.device = :device')
            ->setParameter('device', $device)
            ->getQuery()
            ->getSingleScalarResult();

        $this->assertSame(0, (int) $count);

        // Approval should be revoked
        $this->em()->refresh($image);
        $this->assertFalse($image->isApprovedForDevice($device));
    }

    public function test_link_throws_DeviceClaimRequiredException_without_consent(): void
    {
        $user1 = $this->createUser('claimreq1@example.com');
        $user2 = $this->createUser('claimreq2@example.com');

        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:04');
        $device->setName('Old');
        $device->setUser($user1);
        $this->em()->persist($device);
        $this->em()->flush();

        $this->expectException(\App\Service\DeviceClaimRequiredException::class);
        $this->service->linkToUser('AA:BB:CC:DD:EE:04', $user2);
    }

    public function test_isClaimedByAnotherUser_returns_true_only_when_owned_by_someone_else(): void
    {
        $owner = $this->createUser('claim-owner@example.com');
        $other = $this->createUser('claim-other@example.com');

        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:05');
        $device->setName('Frame');
        $device->setUser($owner);
        $this->em()->persist($device);
        $this->em()->flush();

        $this->assertFalse($this->service->isClaimedByAnotherUser('AA:BB:CC:DD:EE:05', $owner));
        $this->assertTrue($this->service->isClaimedByAnotherUser('AA:BB:CC:DD:EE:05', $other));
        // Unknown MAC: not claimed by anyone, so not "claimed by another."
        $this->assertFalse($this->service->isClaimedByAnotherUser('FF:FF:FF:FF:FF:FF', $other));
    }

    public function test_link_resets_device_specific_state_on_takeover(): void
    {
        $oldOwner = $this->createUser('takeover-old@example.com');
        $newOwner = $this->createUser('takeover-new@example.com');
        $newOwner->setTimezone('America/Chicago');
        $this->em()->flush();

        $device = new Device();
        $device->setMac('AA:BB:CC:DD:EE:06');
        $device->setName('Living Room');
        $device->setUser($oldOwner);
        $device->setWakeTimes([6 * 60, 22 * 60]);
        $this->em()->persist($device);
        $this->em()->flush();

        $this->service->linkToUser('AA:BB:CC:DD:EE:06', $newOwner, allowClaim: true);

        $this->em()->refresh($device);
        $this->assertSame('', $device->getName(), 'name reset');
        $this->assertSame([12 * 60], $device->getWakeTimes(), 'wakeTimes default to noon-daily');
        $this->assertSame('America/Chicago', $device->getTimezone(), 'timezone matches new owner');
        $this->assertNull($device->getCurrentImage(), 'currentImage reset');
        $this->assertNull($device->getNextPollExpectedAt(), 'next-poll reset');
    }

    public function test_link_creates_new_device_with_noon_daily_default(): void
    {
        $user = $this->createUser('new-device@example.com');
        $user->setTimezone('Europe/Stockholm');
        $this->em()->flush();

        $device = $this->service->linkToUser('AA:BB:CC:DD:EE:09', $user);

        $this->assertSame([12 * 60], $device->getWakeTimes(), 'noon-daily default');
        $this->assertSame('Europe/Stockholm', $device->getTimezone(), 'inherits user tz');
    }

    public function test_link_falls_back_to_UTC_when_user_has_no_timezone(): void
    {
        $user = $this->createUser('no-tz@example.com');
        // Force the user's timezone to empty to exercise the fallback —
        // some legacy User rows may have NULL/blank timezone.
        $ref = new \ReflectionProperty(\App\Entity\User::class, 'timezone');
        $ref->setAccessible(true);
        $ref->setValue($user, '');
        $this->em()->flush();

        $device = $this->service->linkToUser('AA:BB:CC:DD:EE:0A', $user);
        $this->assertSame('UTC', $device->getTimezone());
    }
}
