<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\TicketBundle\Controller;

use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Repository\UserGroupRepositoryInterface;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
use Chill\TicketBundle\Entity\StateEnum;
use Chill\TicketBundle\Repository\MotiveRepository;
use Chill\TicketBundle\Repository\TicketACLAwareRepositoryInterface;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;

final readonly class TicketListApiController
{
    private \DateInterval $expectedTimeResponseDelay;

    public function __construct(
        private Security $security,
        private TicketACLAwareRepositoryInterface $ticketRepository,
        private PaginatorFactoryInterface $paginatorFactory,
        private SerializerInterface $serializer,
        private PersonRepository $personRepository,
        private MotiveRepository $motiveRepository,
        private ClockInterface $clock,
        ParameterBagInterface $parameterBag,
        private UserRepositoryInterface $userRepository,
        private UserGroupRepositoryInterface $userGroupRepository,
        private CenterRepositoryInterface $centerRepository,
    ) {
        $this->expectedTimeResponseDelay = new \DateInterval($parameterBag->get('chill_ticket')['ticket']['response_time_exceeded_delay']);
    }

    #[Route('/api/1.0/ticket/ticket/list', name: 'chill_ticket_list_api', methods: ['GET'])]
    public function listTicket(Request $request): JsonResponse
    {
        if (!$this->security->isGranted('ROLE_USER')) {
            throw new AccessDeniedHttpException('Only users are allowed to list tickets.');
        }

        $params = [];

        if ($request->query->has('byTicketId')) {
            $params['byTicketId'] = $request->query->getInt('byTicketId', -1);

            if (-1 === $params['byTicketId']) {
                throw new BadRequestHttpException("The parameter 'byTicketId' is not an integer.");
            }
        }

        if ($request->query->has('byPerson')) {
            $personIds = explode(',', $request->query->get('byPerson'));
            foreach ($personIds as $id) {
                $params['byPerson'][] = $person = $this->personRepository->find($id);

                if (!$this->security->isGranted(PersonVoter::SEE, $person)) {
                    throw new AccessDeniedHttpException(sprintf('Not allowed to see a person with id %d', $id));
                }
            }
        }

        if ($request->query->has('byCurrentState')) {
            try {
                $params['byCurrentState'] = array_map(
                    fn (string $state): StateEnum => StateEnum::fromValue($state),
                    explode(',', $request->query->get('byCurrentState'))
                );
            } catch (\InvalidArgumentException $e) {
                throw new BadRequestHttpException($e->getMessage(), $e);
            }
        }

        if ($request->query->has('byCurrentStateEmergency')) {
            try {
                $params['byCurrentStateEmergency'] = array_map(
                    fn (string $state): EmergencyStatusEnum => EmergencyStatusEnum::fromValue($state),
                    explode(',', $request->query->get('byCurrentStateEmergency'))
                );
            } catch (\InvalidArgumentException $e) {
                throw new BadRequestHttpException($e->getMessage(), $e);
            }
        }

        if ($request->query->has('byMotives')) {
            $motivesIds = explode(',', $request->query->get('byMotives'));
            foreach ($motivesIds as $id) {
                if (!is_numeric($id) || 0 === ((int) $id)) {
                    throw new BadRequestHttpException('Only numbers are allowed in by motives parameter');
                }
                $params['byMotives'][] = $motive = $this->motiveRepository->find($id);

                if (null === $motive) {
                    throw new BadRequestHttpException('Motive not found');
                }
            }
        }

        if ($request->query->has('byCreatedBefore')) {
            $params['byCreatedBefore'] =
                \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $request->query->get('byCreatedBefore'));

            if (false === $params['byCreatedBefore']) {
                $params['byCreatedBefore'] = \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $request->query->get('byCreatedBefore'));
            }
            if (false === $params['byCreatedBefore']) {
                throw new BadRequestHttpException('Invalid date for byCreatedBefore');
            }
        }

        if ($request->query->has('byCreatedAfter')) {
            $params['byCreatedAfter'] =
                \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339, $request->query->get('byCreatedAfter'));
            if (false === $params['byCreatedAfter']) {
                $params['byCreatedAfter'] =
                    \DateTimeImmutable::createFromFormat(\DateTimeImmutable::RFC3339_EXTENDED, $request->query->get('byCreatedAfter'));
            }
            if (false === $params['byCreatedAfter']) {
                throw new BadRequestHttpException('Invalid date for byCreatedAfter');
            }
        }

        if ($request->query->has('byResponseTimeExceeded')) {
            $params['byCurrentState'] = [StateEnum::OPEN];
            $params['byCreatedBefore'] = $this->clock->now()->sub($this->expectedTimeResponseDelay);
            unset($params['byCreatedAfter']);
        }

        if ($request->query->has('byAddresseeToMe')) {
            $params['byAddressee'][] = $this->security->getUser();
        }

        if ($request->query->has('byAddressee')) {
            $userIds = explode(',', $request->query->get('byAddressee'));
            foreach ($userIds as $id) {
                if (!is_numeric($id) || 0 === ((int) $id)) {
                    throw new BadRequestHttpException('Only numbers are allowed in by addressee parameter');
                }

                $params['byAddressee'][] = $user = $this->userRepository->find($id);
                if (null === $user) {
                    throw new BadRequestHttpException('User not found');
                }
            }
        }

        if ($request->query->has('byCreator')) {
            $userIds = explode(',', $request->query->get('byCreator'));
            foreach ($userIds as $id) {
                if (!is_numeric($id) || 0 === ((int) $id)) {
                    throw new BadRequestHttpException('Only numbers are allowed in by creator parameter');
                }

                $params['byCreator'][] = $user = $this->userRepository->find($id);
                if (null === $user) {
                    throw new BadRequestHttpException('User not found');
                }
            }
        }

        if ($request->query->has('byAddresseeGroup')) {
            $groupIds = explode(',', $request->query->get('byAddresseeGroup'));
            foreach ($groupIds as $id) {
                if (!is_numeric($id) || 0 === ((int) $id)) {
                    throw new BadRequestHttpException('Only numbers are allowed in by addressee group parameter');
                }

                $group = $this->userGroupRepository->find($id);
                if (null === $group) {
                    throw new BadRequestHttpException('User group not found');
                }
                $params['byAddresseeGroup'][] = $group;
            }
        }

        if ($request->query->has('byPersonCenter')) {
            $centerIds = explode(',', $request->query->get('byPersonCenter'));
            foreach ($centerIds as $id) {
                if (!is_numeric($id) || 0 === ((int) $id)) {
                    throw new BadRequestHttpException('Only numbers are allowed in by center parameter');
                }

                $center = $this->centerRepository->find($id);
                if (null === $center) {
                    throw new BadRequestHttpException('Center not found');
                }
                $params['byPersonCenter'][] = $center;
            }
        }

        $nb = $this->ticketRepository->countTickets($params);
        $paginator = $this->paginatorFactory->create($nb);

        $tickets = $this->ticketRepository->findTickets($params, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());

        $collection = new Collection($tickets, $paginator);

        return new JsonResponse(
            $this->serializer->serialize($collection, 'json', ['groups' => 'read:simple']),
            json: true
        );
    }
}
