<?php

namespace App\Controller;

use App\Entity\Device;
use App\Entity\User;
use App\Repository\DeviceRepository;
use App\Security\DeviceCredentialService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

#[Route('/api/v1/parent/devices')]
final class ParentDeviceController extends AbstractController
{
    public function __construct(
        private readonly DeviceRepository $devices,
        private readonly DeviceCredentialService $credentials,
        private readonly EntityManagerInterface $entityManager,
        private readonly CsrfTokenManagerInterface $csrf,
    ) {
    }

    #[Route('', name: 'api_parent_devices_index', methods: ['GET'])]
    public function index(): JsonResponse
    {
        $family = $this->parent()->getFamily();

        return new JsonResponse(['devices' => array_map($this->serialize(...), $this->devices->findForFamily($family))]);
    }

    #[Route('', name: 'api_parent_devices_create', methods: ['POST'])]
    public function create(Request $request): JsonResponse
    {
        if (!$this->validCsrf($request)) {
            return new JsonResponse(['error' => 'invalid_csrf_token'], 403);
        }

        $payload = $this->payload($request);
        $label = trim((string) ($payload['label'] ?? ''));
        if ('' === $label || mb_strlen($label) > 100) {
            return new JsonResponse(['error' => 'invalid_device_label'], 422);
        }

        $device = new Device($this->parent()->getFamily(), $label);
        $pairing = $this->credentials->createPairingCode();
        $device->issuePairingCode($pairing['digest'], $pairing['expiresAt']);
        $this->entityManager->persist($device);
        $this->entityManager->flush();

        return new JsonResponse([
            'device' => $this->serialize($device),
            'pairingCode' => $pairing['code'],
            'pairingExpiresAt' => $pairing['expiresAt']->format(DATE_ATOM),
        ], Response::HTTP_CREATED);
    }

    #[Route('/{id<\d+>}/pairing-code', name: 'api_parent_devices_pairing_code', methods: ['POST'])]
    public function pairingCode(int $id, Request $request): JsonResponse
    {
        if (!$this->validCsrf($request)) {
            return new JsonResponse(['error' => 'invalid_csrf_token'], 403);
        }

        $device = $this->devices->findOneForFamily($id, $this->parent()->getFamily());
        if (null === $device) {
            return new JsonResponse(['error' => 'device_not_found'], 404);
        }

        $pairing = $this->credentials->createPairingCode();
        $device->issuePairingCode($pairing['digest'], $pairing['expiresAt']);
        $this->entityManager->flush();

        return new JsonResponse([
            'device' => $this->serialize($device),
            'pairingCode' => $pairing['code'],
            'pairingExpiresAt' => $pairing['expiresAt']->format(DATE_ATOM),
        ]);
    }

    #[Route('/{id<\d+>}/revoke', name: 'api_parent_devices_revoke', methods: ['POST'])]
    public function revoke(int $id, Request $request): JsonResponse
    {
        if (!$this->validCsrf($request)) {
            return new JsonResponse(['error' => 'invalid_csrf_token'], 403);
        }

        $device = $this->devices->findOneForFamily($id, $this->parent()->getFamily());
        if (null === $device) {
            return new JsonResponse(['error' => 'device_not_found'], 404);
        }

        $device->revoke();
        $this->entityManager->flush();

        return new JsonResponse(['device' => $this->serialize($device)]);
    }

    /** @return array<string, mixed> */
    private function payload(Request $request): array
    {
        try {
            return $request->toArray();
        } catch (\Throwable) {
            return [];
        }
    }

    private function validCsrf(Request $request): bool
    {
        $value = $request->headers->get('X-CSRF-Token');

        return is_string($value) && $this->csrf->isTokenValid(new CsrfToken('parent_api', $value));
    }

    private function parent(): User
    {
        $user = $this->getUser();
        if (!$user instanceof User) {
            throw $this->createAccessDeniedException();
        }

        return $user;
    }

    /** @return array<string, mixed> */
    private function serialize(Device $device): array
    {
        return [
            'id' => $device->getId(),
            'label' => $device->getLabel(),
            'status' => $device->getStatus()->value,
            'lastSeenAt' => $device->getLastSeenAt()?->format(DATE_ATOM),
        ];
    }
}
