<?php

declare(strict_types=1);

namespace Gls\GlsPoland\PrestaShop\Installer;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Gls\GlsPoland\Carrier\DTO\AdditionalServices;
use Gls\GlsPoland\Carrier\CarrierType;
use Gls\GlsPoland\PrestaShop\Message\CreateCourierCarrierCommand;
use Gls\GlsPoland\PrestaShop\Message\CreateShopDeliveryCarrierCommand;
use Gls\GlsPoland\PrestaShop\Message\DeleteCarrierCommand;
use Gls\GlsPoland\PrestaShop\ObjectModel\Repository\CarrierRepository as PrestaCarrierRepository;
use Gls\GlsPoland\Translation\TranslatorAwareTrait;
use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface;

final class CarrierInstaller implements InstallerInterface, UninstallerInterface
{
    use TranslatorAwareTrait;

    private $bus;
    private $connection;
    private $prestaCarrierRepository;
    private $dbPrefix;

    public function __construct(CommandBusInterface $bus, Connection $connection, PrestaCarrierRepository $prestaCarrierRepository, string $dbPrefix)
    {
        $this->bus = $bus;
        $this->connection = $connection;
        $this->prestaCarrierRepository = $prestaCarrierRepository;
        $this->dbPrefix = $dbPrefix;
    }

    public function install(\Module $module): void
    {
        $hasStandardCourier = false;
        $hasCashOnDeliveryCourier = false;
        $hasShopDelivery = false;

        foreach ($this->findInstalledCarrierRows() as $carrier) {
            if (!$this->isPrestaCarrierPresent((int) $carrier['id_reference'])) {
                continue;
            }

            if (CarrierType::SHOP_DELIVERY === $carrier['type']) {
                $hasShopDelivery = true;
                continue;
            }

            if (CarrierType::COURIER === $carrier['type']) {
                if ((bool) $carrier['cash_on_delivery']) {
                    $hasCashOnDeliveryCourier = true;
                } else {
                    $hasStandardCourier = true;
                }
            }
        }

        try {
            if (!$hasStandardCourier) {
                $this->createStandardCarrier();
            }

            if (!$hasCashOnDeliveryCourier) {
                $this->createCashOnDeliveryCarrier();
            }

            if (!$hasShopDelivery) {
                $this->createShopDeliveryCarrier();
            }
        } catch (\Exception $e) {
            throw new InstallerException($this->getTranslator()->trans('Could not create module\'s carriers.', [], 'Modules.Glspoland.Installer'), 0, $e);
        }
    }

    public function uninstall(\Module $module, bool $isReset): void
    {
        if ($isReset) {
            return;
        }

        try {
            $carriers = $this->findInstalledCarrierRows();
        } catch (TableNotFoundException $e) {
            return;
        }

        foreach ($carriers as $carrier) {
            try {
                $this->deleteCarrier((int) $carrier['id_reference']);
            } catch (\Exception $e) {
                throw new InstallerException($this->getTranslator()->trans('Could not delete carrier (ID: #id#).', ['#id' => (int) $carrier['id_reference']], 'Modules.Glspoland.Installer'), 0, $e);
            }
        }
    }

    private function deleteCarrier(int $referenceId): void
    {
        $command = new DeleteCarrierCommand($referenceId);

        $this->bus->handle($command);
    }

    private function createStandardCarrier(): void
    {
        $command = new CreateCourierCarrierCommand(
            $this->getTranslator()->trans('GLS Courier', [], 'Modules.Glspoland.Carrier'),
            ['PL']
        );

        $this->bus->handle($command);
    }

    private function createCashOnDeliveryCarrier(): void
    {
        $command = new CreateCourierCarrierCommand(
            sprintf(
                '%s - %s',
                $this->getTranslator()->trans('GLS Courier', [], 'Modules.Glspoland.Carrier'),
                $this->getTranslator()->trans('Cash on Delivery', [], 'Modules.Glspoland.Service')
            ),
            ['PL'],
            new AdditionalServices(true)
        );

        $this->bus->handle($command);
    }

    private function createShopDeliveryCarrier(): void
    {
        $command = new CreateShopDeliveryCarrierCommand(
            $this->getTranslator()->trans('GLS ParcelShop', [], 'Modules.Glspoland.Carrier'),
            ['PL']
        );

        $this->bus->handle($command);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function findInstalledCarrierRows(): array
    {
        $statement = $this->connection
            ->createQueryBuilder()
            ->select('*')
            ->from($this->dbPrefix . 'gls_poland_carrier')
            ->execute();

        if (method_exists($statement, 'fetchAllAssociative')) {
            return $statement->fetchAllAssociative();
        }

        return $statement->fetchAll(\PDO::FETCH_ASSOC);
    }

    private function isPrestaCarrierPresent(int $referenceId): bool
    {
        return null !== $this->prestaCarrierRepository->findOneByReferenceId($referenceId);
    }
}
