<?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\CoreBundle\ApiPlatform;

use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Enum\OfferStatus;
use Ferienpass\CoreBundle\Repository\HostRepository;
use Symfony\Bundle\SecurityBundle\Security;

class OfferAuthenticatedReadExtension implements QueryCollectionExtensionInterface
{
    public function __construct(private readonly Security $security, private readonly HostRepository $hosts)
    {
    }

    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
    {
        if (!is_a($resourceClass, OfferInterface::class, true)) {
            return;
        }

        // Is an anonymous user
        if (!\in_array('api:offer:read', $context['groups'] ?? [], true)) {
            return;
        }

        // Is admin user
        if ($this->security->isGranted('ROLE_ADMIN')) {
            return;
        }

        $user = $this->security->getUser();
        $rootAlias = $queryBuilder->getRootAliases()[0];

        // Only show owned offers
        if (!$user instanceof User) {
            $queryBuilder->where(\sprintf('%s.id = 0', $rootAlias));

            return;
        }

        $hosts = $this->hosts->findByUser($user);
        $queryBuilder
            ->innerJoin(\sprintf('%s.hosts', $rootAlias), 'h', Join::WITH, 'h IN (:hosts)')
            ->setParameter('hosts', $hosts)
        ;

        // Defense in depth: a non-admin authenticated caller must only ever see
        // PUBLISHED offers (in addition to host-scoping above). Without this,
        // any future loosening of the group/role wiring would expose
        // unpublished offers. Mirrors the published predicate enforced for
        // anonymous callers in OfferAnonymousReadExtension. Admins are exempt
        // (returned early above).
        $published = OfferStatus::PUBLISHED;
        $queryBuilder
            ->andWhere(\sprintf("JSON_CONTAINS_PATH(%s.status, 'one', '$.%s') = 1", $rootAlias, $published))
        ;
    }
}
