<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CmsBundle\Controller\Frontend;

use Ferienpass\CmsBundle\Wishlist\WishlistSynchronizer;
use Ferienpass\CoreBundle\Entity\User;
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\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

/**
 * Private JSON API backing the account-side wishlist mirror.
 *
 * Wishlists stay a client-side feature; this endpoint exists only so an
 * authenticated user's lists travel across devices. Both actions are stamped
 * strictly per-user (`private, no-store, Vary: Cookie`) so they never enter a
 * shared cache — which is what keeps offer-list/detail pages publicly cacheable
 * (no wishlist state, auth flag, or CSRF token is ever baked into their HTML;
 * the client learns all of that here, at runtime).
 *
 * Lives under `Controller/Frontend/` (not `Page/`) so the path is NOT
 * locale-prefixed and the JS controller can call a single fixed URL.
 */
class WishlistSyncController extends AbstractController
{
    private const CSRF_ID = 'wishlist_sync';

    /**
     * Bootstrap: tells the client whether it's authenticated and, if so, hands
     * it the current server state plus a CSRF token for the subsequent POST.
     */
    #[Route('/_wishlist', name: 'cms_wishlist_state', methods: ['GET'])]
    public function state(
        #[CurrentUser]
        ?User $user,
        WishlistSynchronizer $synchronizer,
        CsrfTokenManagerInterface $csrf,
    ): Response {
        if (!$user instanceof User) {
            return $this->privateJson(['authenticated' => false]);
        }

        // An empty reconcile returns the authoritative set without mutating.
        $state = $synchronizer->reconcile($user, []);

        return $this->privateJson([
            'authenticated' => true,
            'csrfToken' => $csrf->getToken(self::CSRF_ID)->getValue(),
            'wishlists' => $state['wishlists'],
            'deleted' => $state['deleted'],
        ]);
    }

    /**
     * Reconcile the client's full wishlist set into the account (last-write-wins
     * per list) and return the authoritative set to write back.
     */
    #[Route('/_wishlist/sync', name: 'cms_wishlist_sync', methods: ['POST'])]
    public function sync(
        Request $request,
        #[CurrentUser]
        ?User $user,
        WishlistSynchronizer $synchronizer,
        CsrfTokenManagerInterface $csrf,
    ): Response {
        if (!$user instanceof User) {
            return $this->privateJson(['error' => 'unauthorized'], Response::HTTP_UNAUTHORIZED);
        }

        $payload = json_decode((string) $request->getContent(), true);
        if (!\is_array($payload)) {
            $payload = [];
        }

        $token = (string) ($payload['_token'] ?? $request->headers->get('X-CSRF-Token', ''));
        if (!$csrf->isTokenValid(new CsrfToken(self::CSRF_ID, $token))) {
            throw new AccessDeniedException('Invalid CSRF token.');
        }

        $clientLists = $payload['wishlists'] ?? [];
        if (!\is_array($clientLists)) {
            $clientLists = [];
        }

        $state = $synchronizer->reconcile($user, $clientLists);

        return $this->privateJson([
            'wishlists' => $state['wishlists'],
            'deleted' => $state['deleted'],
        ]);
    }

    /**
     * @param array<string, mixed> $data
     */
    private function privateJson(array $data, int $status = Response::HTTP_OK): JsonResponse
    {
        $response = new JsonResponse($data, $status);
        $response->setPrivate();
        $response->headers->addCacheControlDirective('no-store');
        $response->headers->addCacheControlDirective('must-revalidate');
        $response->setMaxAge(0);

        return $response;
    }
}
