<?php

namespace Myvetshop\Module\Clinique\Controller\Api;

use Myvetshop\Module\Clinique\Entity\EstablishmentEvent;
use Myvetshop\Module\Clinique\Entity\MyvetshopClinique;
use Myvetshop\Module\Clinique\Repository\EstablishmentEventRepository;
use Myvetshop\Module\Clinique\Repository\MyvetshopCliniqueRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class EventsGetAllController extends AbstractController
{
    private EstablishmentEventRepository $establishmentEventRepository;

    private MyvetshopCliniqueRepository $myvetshopCliniqueRepository;

    private ValidatorInterface $validator;

    protected function getQueryParamResolver(): OptionsResolver
    {
        $validator = $this->validator;

        $optionResolver = new OptionsResolver();
        $optionResolver->setDefaults([
            'after' => 0,
            'limit' => 500,
        ]);
        $optionResolver->setAllowedTypes('after', ['int', 'string']);
        $optionResolver->setAllowedTypes('limit', ['int', 'string']);
        $optionResolver->setAllowedValues(
            'after',
            function ($value, &$violations = null) use ($validator) {
                $constraints = [new GreaterThanOrEqual(0)];
                $violations = $validator->validate($value, $constraints);

                return 0 === $violations->count();
            }
        );
        $optionResolver->setAllowedValues(
            'limit',
            function ($value, &$violations = null) use ($validator) {
                $constraints = [
                    new GreaterThanOrEqual(1),
                    new LessThanOrEqual(500),
                ];
                $violations = $validator->validate($value, $constraints);

                return 0 === $violations->count();
            }
        );

        return $optionResolver;
    }

    public function __construct(
        EstablishmentEventRepository $establishmentEventRepository,
        MyvetshopCliniqueRepository $myvetshopCliniqueRepository
    ) {
        $this->establishmentEventRepository = $establishmentEventRepository;
        $this->myvetshopCliniqueRepository = $myvetshopCliniqueRepository;
        $this->validator = Validation::createValidator();
    }

    public function __invoke(Request $request): Response
    {
        $clinique = $this->cliniqueAuth($request);

        /* @var array{after: int|numeric-string, limit: int|numeric-string} $queryParams */
        try {
            $queryParams = $this->getQueryParamResolver()->resolve($request->query->all());
        } catch (\Exception $e) {
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'Invalid query param');
        }

        /** @var int|null $globalMaxResultId */
        $globalMaxResultId = $this->establishmentEventRepository->getLastEventIdByClinique($clinique);

        $events = $this->establishmentEventRepository->findEventsByClinique(
            $clinique,
            (int) $queryParams['after'],
            (int) $queryParams['limit']
        );

        if (empty($events)) {
            return new Response('', Response::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
        }

        $maxResultId = \array_reduce(
            $events,
            fn (int $carry, EstablishmentEvent $event): int => (int) \max($carry, $event->getId() ?? 0),
            (int) $queryParams['after'] + 1
        );

        return new JsonResponse(
            \array_map(
                function (EstablishmentEvent $event): array {
                    return [
                        'id' => $event->getId() ?? 0,
                        'event' => $event->event,
                        'customer' => $event->idCustomer ? $event->customerData : null,
                        'order' => $event->idOrder ? $event->orderData : null,
                    ];
                },
                $events
            ),
            Response::HTTP_PARTIAL_CONTENT,
            [
                'Cache-Control' => 'no-store, no-cache, must-revalidate',
                'Content-Range' => 'events ' . ((int) $queryParams['after'] + 1) . '-' . $maxResultId
                    . '/' . ($globalMaxResultId ?? 0),
                'Vary' => 'Authorization',
            ]
        );
    }

    public function cliniqueAuth(Request $request): MyvetshopClinique
    {
        $codePrivilege = $request->getUser();
        $apiKey = $request->getPassword();

        if (!$codePrivilege || !$apiKey) {
            throw new HttpException(Response::HTTP_UNAUTHORIZED, 'No credentials');
        }

        $clinique = $this->myvetshopCliniqueRepository->findByCodePrivilege(\trim($codePrivilege));
        if (!$clinique || $clinique->apiKey !== $apiKey) {
            throw new HttpException(Response::HTTP_UNAUTHORIZED, 'Invalid API Key');
        }

        if ($clinique->deleted) {
            throw new HttpException(Response::HTTP_FORBIDDEN, 'Clinic not available');
        }

        return $clinique;
    }
}
