<?php

namespace Myvetshop\Module\Clinique\Api\Insight\Controller;

use Myvetshop\Module\Clinique\Api\Insight\Repository\InsightOrderDiscountRepository;
use Myvetshop\Module\Clinique\Api\Insight\Repository\InsightOrderPaymentRepository;
use Myvetshop\Module\Clinique\Api\Insight\Repository\InsightOrderProductRepository;
use Myvetshop\Module\Clinique\Api\Insight\Repository\InsightOrderRefundRepository;
use Myvetshop\Module\Clinique\Api\Insight\Repository\InsightOrderRepository;
use Myvetshop\Module\Clinique\Api\Insight\Serializer\InsightOrderSerializer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\OptionsResolver\OptionsResolver;

class InsightOrdersController extends AbstractController
{
    private const QUERY_LIMIT = 1000;
    private const RESPONSE_LIMIT = 5000;

    private InsightOrderRepository $orderRepository;
    private InsightOrderProductRepository $orderProductRepository;
    private InsightOrderDiscountRepository $orderDiscountRepository;
    private InsightOrderRefundRepository $orderRefundRepository;
    private InsightOrderPaymentRepository $orderPaymentRepository;

    /** @return \DateTimeImmutable|list<int>|int */
    private function normalizeQueryParam(string $name, string $value)
    {
        switch ($name) {
            case 'datemin':
            case 'datemax':
                $normalizedValue = \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $value);
                break;
            case 'ids':
                $normalizedValueRaw = \array_map(
                    fn ($str) => (int) $str == $str ? (int) $str : null,
                    \explode(',', $value) ?: [],
                );
                $normalizedValue = \array_filter($normalizedValueRaw);
                $normalizedValue = $normalizedValueRaw === $normalizedValue ? $normalizedValue : false;
                break;
            case 'page':
                $normalizedValue = (int) $value;
                break;
            default:
                $normalizedValue = false;
        }

        if (false === $normalizedValue) {
            throw new \InvalidArgumentException(\sprintf('Invalid parameter « %s »', $name));
        }

        return $normalizedValue;
    }

    public function __construct(
        InsightOrderRepository $orderRepository,
        InsightOrderProductRepository $orderProductRepository,
        InsightOrderDiscountRepository $orderDiscountRepository,
        InsightOrderRefundRepository $orderRefundRepository,
        InsightOrderPaymentRepository $orderPaymentRepository
    ) {
        $this->orderRepository = $orderRepository;
        $this->orderProductRepository = $orderProductRepository;
        $this->orderDiscountRepository = $orderDiscountRepository;
        $this->orderRefundRepository = $orderRefundRepository;
        $this->orderPaymentRepository = $orderPaymentRepository;
    }

    public function __invoke(Request $request): Response
    {
        // Récupération des paramètres de requête - Préparation du resolver
        $optionResolver = new OptionsResolver();

        // Récupération des paramètres de requête - Définition des paramètres
        $optionResolver->setDefined(['datemin', 'datemax', 'ids', 'page']);
        foreach ($optionResolver->getDefinedOptions() as $param) {
            $optionResolver->setNormalizer($param, fn ($o, $v) => $this->normalizeQueryParam($param, $v));
        }

        // Récupération des paramètres de requête - Résolution
        try {
            $queryParams = $optionResolver->resolve($request->query->all());
            $params = [
                'datemin' => $queryParams['datemin'] ?? null,
                'datemax' => $queryParams['datemax'] ?? null,
                'ids' => $queryParams['ids'] ?? null,
                'page' => (int) ($queryParams['page'] ?? 0),
            ];
        } catch (\InvalidArgumentException $e) {
            return new Response($e->getMessage(), Response::HTTP_BAD_REQUEST);
        }

        // Gestion de la pagination
        $totalCount = $this->orderRepository->countPeriod($params['datemin'], $params['datemax'], $params['ids']);
        $rangeStart = $params['page'] * self::RESPONSE_LIMIT;
        $rangeEnd = \min($rangeStart + self::RESPONSE_LIMIT, $totalCount) - 1;

        return new StreamedResponse(
            function () use ($params, $rangeStart, $rangeEnd) {
                $serializer = new InsightOrderSerializer();

                // Ouverture du json
                echo '[';

                for ($offset = $rangeStart; $offset < $rangeEnd + 1; $offset += self::QUERY_LIMIT) {
                    // Ajout du séparateur entre les séries de commandes
                    echo $offset == $rangeStart ? '' : ',';

                    // Récupération des commandes
                    $orders = $this->orderRepository->findPeriod(
                        $params['datemin'],
                        $params['datemax'],
                        $params['ids'],
                        self::QUERY_LIMIT,
                        $offset,
                    );

                    // Préparation des commandes avec ajout des sous éléments liés
                    $orderIds = \array_map(fn ($o) => $o['id'], $orders);
                    $orders = InsightOrderSerializer::prepareOrdersArray($orders, [
                        'products' => $this->orderProductRepository->findBy(['order_id' => $orderIds]),
                        'discounts' => $this->orderDiscountRepository->findBy(['order_id' => $orderIds]),
                        'refunds' => $this->orderRefundRepository->findBy(['order_id' => $orderIds]),
                        'payments' => $this->orderPaymentRepository->findBy(['order_id' => $orderIds]),
                    ]);

                    // Sérialisation du lot de commande
                    $serialisedOrders = $serializer->serialize($orders);

                    // Renvoi du lot de commande sans les crochets ouvrant et fermant pour les intégrer aux autres lots
                    echo trim($serialisedOrders, '[]');
                }

                // Fermeture du json
                echo ']';
            },
            Response::HTTP_OK,
            [
                'Content-Type' => 'application/json',
                'Content-Range' => \sprintf('items %u-%u/%u', $rangeStart, $rangeEnd, $totalCount),
            ]
        );
    }
}
