<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\Device;
use App\Entity\DeviceImageHistory;
use App\Entity\Image;
use App\Entity\User;
use App\Repository\DeviceRepository;
use Doctrine\ORM\EntityManagerInterface;

class DeviceService
{
    public function __construct(
        private readonly DeviceRepository $repo,
        private readonly EntityManagerInterface $em,
    ) {}

    /**
     * True when the MAC is already claimed by a *different* user. Used by the
     * setup flow to gate the "Claim this frame" checkbox so a friend can't
     * silently take ownership without confirmation.
     */
    public function isClaimedByAnotherUser(string $mac, User $candidate): bool
    {
        $device = $this->repo->findOneBy(['mac' => strtoupper($mac)]);
        return $device !== null
            && $device->getUser() !== null
            && $device->getUser()->getId() !== $candidate->getId();
    }

    /**
     * Atomically link a MAC address to a user.
     *
     * If the device is already owned by a *different* user, this is an
     * ownership transfer — and we require explicit consent via $allowClaim,
     * since the transfer permanently purges the prior owner's history and
     * image-device approvals for this device. The setup page surfaces this
     * as a "Claim this frame" checkbox.
     *
     * @throws DeviceClaimRequiredException when transfer is needed but
     *         $allowClaim is false. Callers should re-render the setup
     *         page with the checkbox visible and a message.
     */
    public function linkToUser(string $mac, User $newOwner, bool $allowClaim = false): Device
    {
        $mac = strtoupper($mac);
        $device = $this->repo->findOneBy(['mac' => $mac]);

        if ($device === null) {
            // Default new devices to "once a day at noon, in the user's
            // timezone." Prior default was a 1440-minute *interval* anchored
            // to last-seen time, which meant gift recipients saw their first
            // photo at 3am if the device happened to set up at 3am — bad
            // first-impression. Noon-daily is forgiving and predictable.
            $device = new Device();
            $device->setMac($mac);
            $device->setTimezone($newOwner->getTimezone() ?: 'UTC');
            $device->setWakeTimes([12 * 60]);
        } elseif ($device->getUser() !== null && $device->getUser()->getId() !== $newOwner->getId()) {
            if (!$allowClaim) {
                throw new DeviceClaimRequiredException();
            }
            // Ownership transfer: purge prior image history for this device.
            $this->purgeDeviceHistory($device);
            // Reset device-specific state so the new owner doesn't inherit
            // schedule, locked image, current image, or pending poll info.
            // Schedule reset matches the new-device default (noon-daily in
            // the new owner's timezone) so the new owner gets the same
            // forgiving first-image behavior.
            $device->setLockedImage(null);
            $device->setCurrentImage(null);
            $device->setCurrentImageOrientation(null);
            $device->setCurrentRenderedAt(null);
            $device->setNextPollExpectedAt(null);
            $device->setName('');
            $device->setTimezone($newOwner->getTimezone() ?: 'UTC');
            $device->setWakeTimes([12 * 60]);
        }

        $device->setUser($newOwner);
        $device->setLinkedAt(new \DateTimeImmutable());
        $this->em->persist($device);
        $this->em->flush();

        return $device;
    }

    /**
     * Owner-initiated delete. Revokes image-device approvals, drops history,
     * and removes the Device row entirely so the MAC is fully unclaimed —
     * the next provisioning poll from this device will return 404 and the
     * frame will display the setup QR for whoever provisions it next.
     *
     * The user-facing pitch is "I'm selling/giving this away" — by purging
     * before handing the device over, the new owner can't accidentally pull
     * the seller's photos even if the seller forgets to wipe NVS first.
     */
    public function deleteDeviceForOwner(Device $device): void
    {
        $this->purgeDeviceHistory($device);
        $this->em->remove($device);
        $this->em->flush();
    }

    /** Remove all image approvals and display history for this device. */
    private function purgeDeviceHistory(Device $device): void
    {
        // Remove device from every image's approval list
        $images = $this->em->createQueryBuilder()
            ->select('i')
            ->from(Image::class, 'i')
            ->join('i.approvedDevices', 'd')
            ->where('d = :device')
            ->setParameter('device', $device)
            ->getQuery()
            ->getResult();

        foreach ($images as $image) {
            $image->revokeForDevice($device);
        }

        // Purge display history (no ORM cascade fires on ownership transfer)
        $this->em->createQueryBuilder()
            ->delete(DeviceImageHistory::class, 'h')
            ->where('h.device = :device')
            ->setParameter('device', $device)
            ->getQuery()
            ->execute();
    }
}
