<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Order\EventListener\Admin;

use Doctrine\DBAL\Query\QueryBuilder;
use InPost\International\Configuration\Repository\ApiConfigurationRepositoryInterface;
use InPost\International\Controller\Admin\PickupOrderController;
use InPost\International\Controller\Admin\ShipmentController;
use InPost\International\Hook\Event\Admin\DisplayGridTableAfterEvent;
use InPost\International\Order\Event\Admin\BuildGridDefinitionEvent;
use InPost\International\Order\Event\Admin\BuildOrdersQueriesEvent;
use InPost\International\PrestaShop\Grid\Action\Bulk\ModalFormSubmitBulkAction as CustomModalFormSubmitBulkAction;
use PrestaShop\PrestaShop\Core\Grid\Action\Bulk\BulkActionInterface;
use PrestaShop\PrestaShop\Core\Grid\Action\Bulk\Type\SubmitBulkAction;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\DateTimeColumn;
use PrestaShop\PrestaShop\Core\Grid\Filter\Filter;
use PrestaShop\PrestaShop\Core\Util\DateTime\DateTime;
use PrestaShopBundle\Form\Admin\Type\DateRangeType;
use PrestaShopBundle\Security\Voter\PageVoter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class ModifyOrderGridListener implements EventSubscriberInterface
{
    /**
     * @var AuthorizationCheckerInterface
     */
    private $authorizationChecker;

    /**
     * @var ApiConfigurationRepositoryInterface
     */
    private $configuration;

    /**
     * @var Environment
     */
    private $twig;

    /**
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * @var string
     */
    private $dbPrefix;

    public function __construct(AuthorizationCheckerInterface $authorizationChecker, ApiConfigurationRepositoryInterface $configuration, Environment $twig, TranslatorInterface $translator, string $dbPrefix)
    {
        $this->authorizationChecker = $authorizationChecker;
        $this->configuration = $configuration;
        $this->twig = $twig;
        $this->translator = $translator;
        $this->dbPrefix = $dbPrefix;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            BuildGridDefinitionEvent::NAME => 'modifyGridDefinition',
            BuildOrdersQueriesEvent::NAME => 'modifyQueries',
            DisplayGridTableAfterEvent::NAME => 'onDisplayGridTableAfter',
        ];
    }

    public function modifyGridDefinition(BuildGridDefinitionEvent $event): void
    {
        if (!$this->authorizationChecker->isGranted(PageVoter::READ, ShipmentController::TAB_NAME)) {
            return;
        }

        $definition = $event->getDefinition();

        $definition->getColumns()->addAfter('date_add', (new DateTimeColumn('inpost_intl_shipment_created_at'))
            ->setName($this->translator->trans('InPost Intl. shipment created at', [], 'Modules.Inpostinternational.Order'))
            ->setOptions([
                'field' => 'inpost_intl_shipment_created_at',
                'empty_data' => '--',
            ])
        );

        $definition->getFilters()->add(
            (new Filter('inpost_intl_shipment_created_at', DateRangeType::class))
                ->setTypeOptions([
                    'required' => false,
                    'property_path' => '[inpost_intl][shipment][created_at]',
                ])
                ->setAssociatedColumn('inpost_intl_shipment_created_at')
        );

        $bulkActions = $definition->getBulkActions();
        foreach ($this->getBulkActions($event->getPrestaShopVersion()) as $action) {
            $bulkActions->add($action);
        }
    }

    public function modifyQueries(BuildOrdersQueriesEvent $event): void
    {
        if (!$this->authorizationChecker->isGranted(PageVoter::READ, ShipmentController::TAB_NAME)) {
            return;
        }

        $this
            ->joinShipmentsTable($searchQb = $event->getSearchQueryBuilder())
            ->addSelect('IFNULL(MIN(inpost_intl_s.created_at), :empty_date) AS inpost_intl_shipment_created_at')
            ->groupBy('o.id_order')
            ->setParameter('empty_date', defined(DateTime::class . '::NULL_DATETIME') ? DateTime::NULL_DATETIME : DateTime::NULL_VALUE);

        $filters = $event->getSearchCriteria()->getFilters();
        $filters = $filters['inpost_intl'] ?? [];

        if (!isset($filters['shipment'])) {
            return;
        }

        $this->joinShipmentsTable($countQb = $event->getCountQueryBuilder());

        $this->applyFilters($searchQb, $filters);
        $this->applyFilters($countQb, $filters);
    }

    public function onDisplayGridTableAfter(DisplayGridTableAfterEvent $event): void
    {
        if ('order' !== $event->getGrid()->getId()) {
            return;
        }

        if (!$this->authorizationChecker->isGranted(PageVoter::CREATE, PickupOrderController::TAB_NAME)) {
            return;
        }

        $content = $this->twig->render('@Modules/inpostinternational/views/templates/hook/admin/orders/grid_modals.html.twig');
        $event->appendContent($content);
    }

    /**
     * @return iterable<BulkActionInterface>
     */
    private function getBulkActions(string $psVersion): iterable
    {
        if (!$this->configuration->hasConfiguration()) {
            return;
        }

        if ($this->authorizationChecker->isGranted(PageVoter::CREATE, ShipmentController::TAB_NAME)) {
            yield $this->createBulkAction('inpost_intl_create_shipments', $this->translator->trans('InPost International: create shipments', [], 'Modules.Inpostinternational.Order'), [
                'submit_route' => 'admin_inpost_intl_orders_bulk_create_shipments',
            ], 'local_shipping');

            yield $this->createBulkAction('inpost_intl_create_shipments_and_print_labels', $this->translator->trans('InPost International: create shipments and print labels', [], 'Modules.Inpostinternational.Order'), [
                'submit_route' => 'admin_inpost_intl_orders_bulk_create_shipments',
                'route_params' => [
                    'print_labels' => true,
                ],
            ], 'local_shipping');
        }

        yield $this->createBulkAction('inpost_intl_print_labels', $this->translator->trans('InPost International: print labels', [], 'Modules.Inpostinternational.Order'), [
            'submit_route' => 'admin_inpost_intl_orders_bulk_print_labels',
        ], 'print');

        yield $this->createBulkAction('inpost_intl_update_shipment_status', $this->translator->trans('InPost International: update shipment statuses', [], 'Modules.Inpostinternational.Order'), [
            'submit_route' => 'admin_inpost_intl_orders_bulk_update_status',
        ], 'refresh');

        if ($this->shouldPickupOrderActionBeAdded($psVersion)) {
            yield $this->createBulkAction('inpost_intl_create_pickup_order', $this->translator->trans('InPost International: create pickup order', [], 'Modules.Inpostinternational.Order'), [
                'modal_id' => 'inpost_intl_create_pickup_order_modal',
                'submit_route' => 'admin_inpost_intl_pickup_orders_create',
                'class' => 'js-inpost-intl-create-pickup-order-btn',
            ], 'local_shipping', CustomModalFormSubmitBulkAction::class);
        }
    }

    /**
     * @template T of BulkActionInterface
     *
     * @param class-string<T> $class
     *
     * @return T
     */
    private function createBulkAction(string $id, string $name, array $options, string $icon, string $class = SubmitBulkAction::class): BulkActionInterface
    {
        $action = (new $class($id))
            ->setName($name)
            ->setOptions($options);

        // added in PS 8
        if (is_callable([$action, 'setIcon'])) {
            $action->setIcon($icon);
        }

        return $action;
    }

    private function joinShipmentsTable(QueryBuilder $qb): QueryBuilder
    {
        return $qb
            ->leftJoin('o', $this->dbPrefix . 'inpost_intl_shipment', 'inpost_intl_s', 'inpost_intl_s.id_order = o.id_order AND inpost_intl_s.is_sandbox = :inpost_intl_sandbox')
            ->setParameter('inpost_intl_sandbox', $this->configuration->getConfiguration()->getEnvironment()->isTestEnvironment());
    }

    private function applyFilters(QueryBuilder $qb, array $filters): void
    {
        $filter = $filters['shipment']['created_at'];

        if (isset($filter['from'])) {
            $qb->andWhere('inpost_intl_s.created_at >= :inpost_intl_shipment_created_from');
            $qb->setParameter('inpost_intl_shipment_created_from', sprintf('%s 00:00:00', $filter['from']));
        }

        if (isset($filter['to'])) {
            $qb->andWhere('inpost_intl_s.created_at <= :inpost_intl_shipment_created_to');
            $qb->setParameter('inpost_intl_shipment_created_to', sprintf('%s 23:59:59', $filter['to']));
        }
    }

    private function shouldPickupOrderActionBeAdded(string $psVersion): bool
    {
        // the "displayAdminGridTableAfter" hook was added in 1.7.8
        if (\Tools::version_compare($psVersion, '1.7.8')) {
            return false;
        }

        return $this->authorizationChecker->isGranted(PageVoter::CREATE, PickupOrderController::TAB_NAME);
    }
}
