<?php

declare(strict_types=1);

namespace Gls\GlsPoland\Controller\Admin;

use Gls\GlsPoland\AdePlus\Fault\AdePlusFault;
use Gls\GlsPoland\Entity\Consignment;
use Gls\GlsPoland\Form\Type\CreateConsignmentType;
use Gls\GlsPoland\Message\BulkCreateConsignmentsCommand;
use Gls\GlsPoland\MessageHandler\Consignment\CreateConsignmentsResult;
use Gls\GlsPoland\PrestaShop\Order\CreateConsignmentCommandFactoryInterface;
use Gls\GlsPoland\PrestaShop\Order\Exception\OrderNotFoundException;
use Gls\GlsPoland\Translation\TranslatorAwareTrait;
use PrestaShop\PrestaShop\Adapter\Admin\UrlGenerator;
use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface;
use PrestaShopBundle\Security\Voter\PageVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

/**
 * @Route(path="orders", name="admin_gls_poland_orders_")
 */
final class OrderController extends AbstractController
{
    use TranslatorAwareTrait;

    /**
     * @Route(path="/{id}/new-consignment", name="create_consignment", methods={"GET", "POST"}, requirements={"id"="\d+"})
     */
    public function createConsignment(int $id, Request $request, CommandBusInterface $bus, CreateConsignmentCommandFactoryInterface $commandFactory): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::CREATE, ConsignmentController::TAB_NAME);

        $lightDisplay = $request->query->getBoolean('liteDisplaying');

        $options = [
            'action' => $this->generateUrl('admin_gls_poland_orders_create_consignment', [
                'id' => $id,
                'liteDisplaying' => $lightDisplay,
            ]),
        ];

        if ($partialSubmit = $request->query->has('partial_submit')) {
            $options['validation_groups'] = false;
        }

        try {
            $command = $commandFactory->create($id);
        } catch (OrderNotFoundException $e) {
            $this->addFlash('error', $this->getTranslator()->trans('Order #{id} not found.', ['#{id}' => $id], 'Modules.Glspoland.Errors'));

            return $this->redirectToRoute('admin_orders_index', [], Response::HTTP_SEE_OTHER);
        } catch (\Throwable $e) {
            $this->addFlash('error', $e->getMessage());

            return $this->redirectToRoute('admin_orders_index', [], Response::HTTP_SEE_OTHER);
        }

        $form = $this
            ->createForm(CreateConsignmentType::class, $command, $options)
            ->handleRequest($request);

        if (!$partialSubmit && $form->isSubmitted() && $form->isValid()) {
            try {
                /** @var Consignment $consignment */
                $consignment = $bus->handle($form->getData());

                $this->addFlash('success', $this->getTranslator()->trans('Successful creation', [], 'Admin.Notifications.Success'));

                if ($lightDisplay) {
                    return $this->render('@Modules/glspoland/views/templates/admin/consignment/success.html.twig', [
                        'consignment_id' => $consignment->getId(),
                    ]);
                }

                return $this->redirectToRoute('admin_gls_poland_orders_create_consignment', ['id' => $id], Response::HTTP_SEE_OTHER);
            } catch (\Throwable $fault) {
                $this->addFlash('error', $fault->getMessage());
            }
        }

        if (!$partialSubmit && $form->isSubmitted() && !$form->isValid()) {
            foreach ($form->getErrors(true) as $error) {
                $origin = $error->getOrigin();
                $fieldName = null !== $origin ? (string) $origin->getName() : 'form';

                $this->addFlash('error', sprintf('%s: %s', $fieldName, $error->getMessage()));
            }
        }

        return $this->render('@Modules/glspoland/views/templates/admin/consignment/form.html.twig', [
            'form' => $form->createView(),
            'light_display' => $lightDisplay,
        ]);
    }

    /**
     * @Route(path="/bulk-create-consignments", name="bulk_create_consignments", methods={"POST"})
     */
    public function bulkCreateConsignments(Request $request, CommandBusInterface $bus): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::CREATE, ConsignmentController::TAB_NAME);

        $orderIds = $this->getOrderIdsFromRequest($request);

        /** @var CreateConsignmentsResult $result */
        $result = $bus->handle(new BulkCreateConsignmentsCommand(...$orderIds));

        if ([] === $errors = $result->getErrors()) {
            $this->addFlash('success', $this->getTranslator()->trans('Successful creation', [], 'Admin.Notifications.Success'));
        } else {
            foreach ($errors as $orderId => $error) {
                $this->addFlash('error', $this->getTranslator()->trans('Order #{orderId}: {error}', ['{orderId}' => $orderId, '{error}' => $error->getMessage()], 'Modules.Glspoland.Errors'));
            }
        }

        return $this->redirectToRoute('admin_orders_index', [], Response::HTTP_SEE_OTHER);
    }

    /**
     * @Route(path="/legacy/bulk-create-consignments", name="legacy_bulk_create_consignments", methods={"POST"})
     *
     * @param UrlGenerator $legacyUrlGenerator
     */
    public function legacyBulkCreateConsignments(Request $request, CommandBusInterface $bus, UrlGeneratorInterface $legacyUrlGenerator): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::CREATE, ConsignmentController::TAB_NAME);

        $orderIds = $this->getOrderIdsFromRequest($request, 'orderBox');

        /** @var CreateConsignmentsResult $result */
        $result = $bus->handle(new BulkCreateConsignmentsCommand(...$orderIds));

        if ([] === $errors = $result->getErrors()) {
            $this->addFlash('glspoland_success', $this->getTranslator()->trans('Successful creation', [], 'Admin.Notifications.Success'));
        } else {
            foreach ($errors as $orderId => $error) {
                $this->addFlash('glspoland_error', $this->getTranslator()->trans('Order #{orderId}: {error}', ['{orderId}' => $orderId, '{error}' => $error->getMessage()], 'Modules.Glspoland.Errors'));
            }
        }

        $url = $legacyUrlGenerator->generate('AdminOrders');

        return $this->redirect($url, Response::HTTP_SEE_OTHER);
    }

    private function getOrderIdsFromRequest(Request $request, string $key = 'order_orders_bulk'): array
    {
        $orderIds = [];

        if (method_exists($request->request, 'all')) {
            $all = $request->request->all();
            if (isset($all[$key]) && is_array($all[$key])) {
                $orderIds = $this->flattenArrayValues($all[$key]);
            }
        }

        if ([] === $orderIds) {
            throw new BadRequestHttpException('Order IDs list is empty.');
        }

        $normalizedOrderIds = [];
        foreach ($orderIds as $orderId) {
            if (!is_scalar($orderId) || '' === trim((string) $orderId)) {
                continue;
            }

            $orderId = (int) $orderId;
            if (0 < $orderId) {
                $normalizedOrderIds[] = $orderId;
            }
        }

        $normalizedOrderIds = array_values(array_unique($normalizedOrderIds));

        if ([] === $normalizedOrderIds) {
            throw new BadRequestHttpException('Order IDs list is empty.');
        }

        return $normalizedOrderIds;
    }

    /**
     * @param array<mixed> $values
     *
     * @return array<mixed>
     */
    private function flattenArrayValues(array $values): array
    {
        $flat = [];

        foreach ($values as $value) {
            if (is_array($value)) {
                $flat = array_merge($flat, $this->flattenArrayValues($value));
                continue;
            }

            $flat[] = $value;
        }

        return $flat;
    }
}