<?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\PrestaShop\ObjectModel\Repository;

use InPost\International\Carrier\CarrierConfiguration;

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

/**
 * @extends AbstractServiceObjectRepository<\Carrier>
 */
class CarrierRepository extends AbstractServiceObjectRepository
{
    public static function getDefaultModelName(): string
    {
        return \Carrier::class;
    }

    public function findOneByReferenceId(int $referenceId, int $languageId = null): ?\Carrier
    {
        if (0 >= $referenceId) {
            return null;
        }

        if (false === $carrier = \Carrier::getCarrierByReference($referenceId, $languageId)) {
            return null;
        }

        return $carrier;
    }

    public function add(CarrierConfiguration $configuration): void
    {
        $carrier = $configuration->getCarrier();

        if (0 < (int) $carrier->id) {
            throw new \DomainException('Carrier is already persisted.');
        }

        try {
            $this->manager->save($carrier);

            if (0 >= $carrier->id_reference) {
                $carrier->id_reference = (int) $carrier->id;
            }

            $this->updateTaxRulesGroup($carrier, $configuration);
            $this->addRanges($carrier, $configuration);
            $this->addZones($carrier, ...$configuration->getZoneIds());
            $this->addGroups($carrier, ...$configuration->getGroupIds());
            $this->updatePaymentModuleRestrictions($carrier, $configuration);
        } catch (\Exception $e) {
            try {
                $this->remove($carrier);
            } finally {
                throw $e;
            }
        }
    }

    public function remove(\Carrier $carrier): void
    {
        if (0 >= (int) $carrier->id) {
            return;
        }

        if ($carrier->isUsed()) {
            $this->softDelete($carrier);
        } else {
            $carrier->id_shop_list = $carrier->getAssociatedShops();
            $this->manager->remove($carrier);
        }
    }

    public function duplicateIfUsed(\Carrier $carrier): \Carrier
    {
        if (!$carrier->isUsed()) {
            return $carrier;
        }

        /** @var \Carrier $newCarrier */
        $newCarrier = $this->execute(static function () use ($carrier) {
            return $carrier->duplicateObject();
        });
        $newCarrier->copyCarrierData($carrier->id);
        $this->execute(static function () use ($newCarrier, $carrier) {
            if (false === $groups = $carrier->getGroups()) {
                return false;
            }

            return $newCarrier->setGroups(array_column($groups, 'id_group'));
        });

        try {
            $this->softDelete($carrier);

            return $newCarrier;
        } catch (\Exception $e) {
            try {
                $this->remove($newCarrier);
            } finally {
                throw $e;
            }
        }
    }

    private function softDelete(\Carrier $carrier): void
    {
        $carrier->deleted = true;
        $carrier->setFieldsToUpdate(['deleted' => true]);
        $this->manager->save($carrier);
    }

    private function updateTaxRulesGroup(\Carrier $carrier, CarrierConfiguration $configuration): void
    {
        if (null === $taxRulesGroupId = $configuration->getTaxRulesGroupId()) {
            return;
        }

        $this->execute(static function () use ($carrier, $taxRulesGroupId) {
            return $carrier->setTaxRulesGroup($taxRulesGroupId);
        });
    }

    private function addRanges(\Carrier $carrier, CarrierConfiguration $configuration): void
    {
        $carrierId = (int) $carrier->id;

        foreach ($configuration->getRanges() as $range) {
            if (0 < (int) $range->id) {
                throw new \DomainException('Range is already persisted.');
            }

            $rangeCarrierId = (int) $range->id_carrier;

            if (0 < $rangeCarrierId && $carrierId !== $rangeCarrierId) {
                throw new \DomainException('Range belongs to another carrier.');
            }

            $range->id_carrier = (int) $carrier->id;
            $this->manager->save($range);
        }
    }

    private function addZones(\Carrier $carrier, int ...$zoneIds): void
    {
        if ([] === $zoneIds) {
            return;
        }

        foreach ($zoneIds as $zoneId) {
            $this->execute(static function () use ($carrier, $zoneId) {
                return $carrier->addZone($zoneId);
            });
        }
    }

    private function addGroups(\Carrier $carrier, int ...$groupIds): void
    {
        if ([] === $groupIds) {
            return;
        }

        $this->execute(static function () use ($carrier, $groupIds) {
            return $carrier->setGroups($groupIds, false);
        });
    }

    private function updatePaymentModuleRestrictions(\Carrier $carrier, CarrierConfiguration $configuration): void
    {
        if (null !== $allowedModuleIds = $configuration->getAllowedPaymentModuleIds()) {
            $this->disablePaymentModulesExcept($carrier, ...$allowedModuleIds);
        }

        if ([] !== $disallowedModuleIds = $configuration->getDisallowedPaymentModuleIds()) {
            $this->disablePaymentModules($carrier, ...$disallowedModuleIds);
        }
    }

    private function disablePaymentModules(\Carrier $carrier, int ...$moduleIds): void
    {
        if ([] === $moduleIds) {
            return;
        }

        $this->getConnection()->delete('module_carrier', [
            'id_reference' => (int) $carrier->id_reference,
            'id_module' => $moduleIds,
        ]);
    }

    private function disablePaymentModulesExcept(\Carrier $carrier, int ...$moduleIds): void
    {
        $criteria = ['id_reference' => (int) $carrier->id_reference];

        if ([] !== $moduleIds) {
            $criteria[] = sprintf('id_module NOT IN (%s)', implode(',', $moduleIds));
        }

        $this->getConnection()->delete('module_carrier', $criteria);
    }
}
