<?php

declare(strict_types=1);

namespace Gls\GlsPoland\Controller\Admin;

use Gls\GlsPoland\Configuration\Api;
use Gls\GlsPoland\Download\SpreadsheetFile;
use Gls\GlsPoland\Download\StreamableFileInterface;
use Gls\GlsPoland\Form\Type\CreatePickupType;
use Gls\GlsPoland\Message\CreatePickupCommand;
use Gls\GlsPoland\Message\DeleteConsignmentCommand;
use Gls\GlsPoland\Message\PrintLabelsCommand;
use Gls\GlsPoland\Message\PrintPickupReceiptCommand;
use Gls\GlsPoland\PrestaShop\Grid\Definition\Factory\FilterableGridDefinitionFactoryInterface;
use Gls\GlsPoland\PrestaShop\Grid\Filters\ConsignmentFilters;
use Gls\GlsPoland\Translation\TranslatorAwareTrait;
use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface;
use PrestaShop\PrestaShop\Core\Grid\Column\ColumnInterface;
use PrestaShop\PrestaShop\Core\Grid\Filter\GridFilterFormFactoryInterface;
use PrestaShop\PrestaShop\Core\Grid\GridFactoryInterface;
use PrestaShop\PrestaShop\Core\Grid\GridInterface;
use PrestaShop\PrestaShop\Core\Grid\Presenter\GridPresenterInterface;
use PrestaShopBundle\Security\Voter\PageVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\OptionsResolver\Exception\NoSuchOptionException;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @Route(path="consignments", name="admin_gls_poland_consignments_", defaults={"_legacy_controller"=ConsignmentController::TAB_NAME, "_legacy_link"=ConsignmentController::TAB_NAME})
 */
final class ConsignmentController extends AbstractController
{
    use TranslatorAwareTrait;

    public const TAB_NAME = 'AdminGlsPolandConsignments';
    public const PARENT_TAB_NAME = 'AdminParentShipping';

    /**
     * @Route(path="/", name="index", methods={"GET"})
     */
    public function index(ConsignmentFilters $filters, Api\ConfigurationInterface $apiConfiguration, GridFactoryInterface $gridFactory, GridPresenterInterface $gridPresenter): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::READ, self::TAB_NAME);

        $filters->addFilter([
            'sandbox' => $apiConfiguration->isSandboxEnabled(),
        ]);

        $grid = $gridFactory->getGrid($filters);
        $createPickupForm = $this->createForm(CreatePickupType::class, new CreatePickupCommand(date('Y-m-d h:i:s')), [
            'action' => $this->generateUrl('admin_gls_poland_consignments_create_pickup'),
        ]);

        return $this->render('@Modules/glspoland/views/templates/admin/consignment/index.html.twig', [
            'grid' => $gridPresenter->present($grid),
            'create_pickup_form' => $createPickupForm->createView(),
        ]);
    }

    /**
     * @Route("/", name="filter", methods={"POST"})
     */
    public function filter(Request $request, FilterableGridDefinitionFactoryInterface $gridDefinitionFactory, GridFilterFormFactoryInterface $filterFormFactory): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::READ, self::TAB_NAME);

        $gridDefinition = $gridDefinitionFactory->getDefinition();
        $filtersForm = $filterFormFactory
            ->create($gridDefinition)
            ->handleRequest($request);

        $redirectParams = [];
        if ($filtersForm->isSubmitted()) {
            $redirectParams[$gridDefinitionFactory->getFilterId()] = [
                'filters' => $filtersForm->getData(),
            ];
        }

        return $this->redirectToRoute('admin_gls_poland_consignments_index', $redirectParams, Response::HTTP_SEE_OTHER);
    }

    /**
     * @Route(path="/export.{_format}", name="export", methods={"GET"}, defaults={"_format": "csv"}, requirements={"_format": "csv|xls|xlsx"})
     */
    public function export(ConsignmentFilters $filters, Api\ConfigurationInterface $apiConfiguration, GridFactoryInterface $gridFactory, string $_format = 'csv'): StreamedResponse
    {
        $this->denyAccessUnlessGranted(PageVoter::READ, self::TAB_NAME);

        $filters = new ConsignmentFilters(['limit' => null] + $filters->all());
        $filters->addFilter([
            'sandbox' => $apiConfiguration->isSandboxEnabled(),
        ]);

        $grid = $gridFactory->getGrid($filters);

        $file = new SpreadsheetFile($_format, function () use ($grid) {
            yield $namesByField = $this->getGridColumnNamesByField($grid);

            foreach ($grid->getData()->getRecords() as $record) {
                yield array_merge($namesByField, array_intersect_key($record, $namesByField));
            }
        });

        return $this->streamFile('consignments', $file);
    }

    /**
     * @Route("/{id}/delete", name="delete", methods={"POST"}, requirements={"id"="\d+"})
     */
    public function delete(int $id, CommandBusInterface $bus): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::DELETE, self::TAB_NAME);

        $bus->handle(new DeleteConsignmentCommand($id));
        $this->addFlash('success', $this->getTranslator()->trans('Successful deletion', [], 'Admin.Notifications.Success'));

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

    /**
     * @Route("/{id}/print-labels", name="print_labels", methods={"GET"}, requirements={"id"="\d+"})
     */
    public function printLabel(int $id, CommandBusInterface $bus): StreamedResponse
    {
        $this->denyAccessUnlessGranted(PageVoter::READ, self::TAB_NAME);

        $labels = $bus->handle(new PrintLabelsCommand($id));

        return $this->streamFile('labels', $labels);
    }

    /**
     * @Route("/create-pickup", name="create_pickup", methods={"POST"})
     */
    public function createPickup(Request $request, CommandBusInterface $bus): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::UPDATE, self::TAB_NAME);

        $form = $this
            ->createForm(CreatePickupType::class)
            ->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $bus->handle($form->getData());
            $this->addFlash('success', $this->getTranslator()->trans('Successful creation.', [], 'Admin.Notifications.Success'));
        } else {
            foreach ($form->getErrors(true) as $error) {
                $this->addFlash('error', sprintf('%s: %s', $error->getOrigin()->getName(), $error->getMessage()));
            }
        }

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

    /**
     * @Route("/{id}/print-pickup-receipt", name="print_pickup_receipt", methods={"GET"}, requirements={"id"="\d+"})
     */
    public function printPickupReceipt(int $id, CommandBusInterface $bus): Response
    {
        $this->denyAccessUnlessGranted(PageVoter::READ, self::TAB_NAME);

        $receipt = $bus->handle(new PrintPickupReceiptCommand($id));

        return $this->streamFile('pickup_receipt', $receipt);
    }

    private function streamFile(string $fileName, StreamableFileInterface $file): StreamedResponse
    {
        $response = new StreamedResponse([$file, 'stream']);

        $disposition = $response->headers->makeDisposition(
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
            sprintf('%s.%s', $fileName, $file->getFileExtension())
        );

        $response->headers->add([
            'Content-Type' => $file->getContentType(),
            'Content-Disposition' => $disposition,
        ]);

        return $response;
    }

    private function getGridColumnNamesByField(GridInterface $grid): array
    {
        $names = [];

        /** @var ColumnInterface $column */
        foreach ($grid->getDefinition()->getColumns() as $column) {
            try {
                $field = $column->getOption('field');
            } catch (NoSuchOptionException $e) {
                continue;
            }

            $names[$field] = $column->getName();
        }

        return $names;
    }
}
