<?php
/**
 * SimplyIN Payment Module
 *
 * DPD shipping module integrator for setting pickup point extension attributes.
 */
declare(strict_types=1);

namespace Simplyin\Payment\Model\Integrator;

use Magento\Framework\Module\Manager as ModuleManager;
use Magento\Quote\Api\Data\CartInterface;
use Psr\Log\LoggerInterface;
use Simplyin\Payment\Api\IntegratorInterface;

/**
 * Class DpdIntegrator
 *
 * Handles integration with the DpdPoland/Dpdshippingpickup and DpdPoland/Dpdshippingswipbox modules.
 * Sets the shippingDpdPickup or shippingDpdSwipbox extension attributes on the shipping Address
 * when a user selects a DPD pickup point or Swipbox locker.
 *
 * @see Requirements 4.1, 4.2, 4.3, 4.4
 */
class DpdIntegrator implements IntegratorInterface
{
    /**
     * Module name for DPD Poland Pickup
     */
    private const MODULE_NAME_PICKUP = 'DpdPoland_Dpdshippingpickup';

    /**
     * Module name for DPD Poland Swipbox
     */
    private const MODULE_NAME_SWIPBOX = 'DpdPoland_Dpdshippingswipbox';

    /**
     * Extension attribute name for DPD pickup
     */
    private const EXTENSION_ATTRIBUTE_DPD_PICKUP = 'shippingDpdPickup';

    /**
     * Extension attribute name for DPD Swipbox
     */
    private const EXTENSION_ATTRIBUTE_DPD_SWIPBOX = 'shippingDpdSwipbox';

    /**
     * Address suffix format for DPD pickup
     */
    private const ADDRESS_SUFFIX_FORMAT = '[DPD_PICKUP:%s]';

    /**
     * Provider name for standard DPD pickup
     */
    private const PROVIDER_DPD = 'dpd';

    /**
     * Provider name for DPD Swipbox
     */
    private const PROVIDER_DPD_SWIPBOX = 'dpd_swipbox';

    /**
     * Alternative provider name for Swipbox
     */
    private const PROVIDER_SWIPBOX = 'swipbox';

    /**
     * @var ModuleManager
     */
    private ModuleManager $moduleManager;

    /**
     * @var LoggerInterface
     */
    private LoggerInterface $logger;

    /**
     * Constructor
     *
     * @param ModuleManager $moduleManager
     * @param LoggerInterface $logger
     */
    public function __construct(
        ModuleManager $moduleManager,
        LoggerInterface $logger
    ) {
        $this->moduleManager = $moduleManager;
        $this->logger = $logger;
    }

    /**
     * Check if any DPD Poland module is available and enabled
     *
     * @return bool True if either DpdPoland_Dpdshippingpickup or DpdPoland_Dpdshippingswipbox is installed and active
     */
    public function isAvailable(): bool
    {
        return $this->moduleManager->isEnabled(self::MODULE_NAME_PICKUP)
            || $this->moduleManager->isEnabled(self::MODULE_NAME_SWIPBOX);
    }

    /**
     * Check if the DPD Pickup module is available
     *
     * @return bool True if DpdPoland_Dpdshippingpickup is enabled
     */
    private function isPickupModuleAvailable(): bool
    {
        return $this->moduleManager->isEnabled(self::MODULE_NAME_PICKUP);
    }

    /**
     * Check if the DPD Swipbox module is available
     *
     * @return bool True if DpdPoland_Dpdshippingswipbox is enabled
     */
    private function isSwipboxModuleAvailable(): bool
    {
        return $this->moduleManager->isEnabled(self::MODULE_NAME_SWIPBOX);
    }

    /**
     * Set the DPD pickup point data on the shipping address
     *
     * Extracts the pickup ID from the lockerId field and sets it as
     * the appropriate extension attribute (shippingDpdPickup or shippingDpdSwipbox)
     * on the shipping address. Also appends the pickup ID to the address suffix.
     *
     * @param CartInterface $quote The quote to set pickup point data on
     * @param array $data Pickup point data containing:
     *                    - lockerId: string - The DPD pickup point ID
     *                    - providerName: string - The provider name (dpd, dpd_swipbox, or swipbox)
     * @return bool True if the pickup point data was successfully set
     */
    public function setPickupPoint(CartInterface $quote, array $data): bool
    {
        if (!$this->isAvailable()) {
            $this->logger->info(
                'DpdIntegrator: No DPD Poland module is enabled, skipping integration'
            );
            return false;
        }

        if (!isset($data['lockerId']) || empty($data['lockerId'])) {
            $this->logger->warning(
                'DpdIntegrator: Missing or empty lockerId in pickup point data'
            );
            return false;
        }

        $pickupId = (string) $data['lockerId'];
        $providerName = strtolower($data['providerName'] ?? $data['provider'] ?? '');

        // Determine if this is a Swipbox or standard DPD pickup
        $isSwipbox = $this->isSwipboxProvider($providerName);

        try {
            $shippingAddress = $quote->getShippingAddress();
            
            if ($shippingAddress === null) {
                $this->logger->warning(
                    'DpdIntegrator: Unable to get shipping address from quote'
                );
                return false;
            }

            $extensionAttributes = $shippingAddress->getExtensionAttributes();
            
            if ($extensionAttributes === null) {
                $this->logger->warning(
                    'DpdIntegrator: Unable to get extension attributes from shipping address'
                );
                return false;
            }

            // Set the appropriate extension attribute based on provider type
            if (!$this->applyPickupExtensionAttribute($extensionAttributes, $isSwipbox, $pickupId)) {
                return false;
            }

            $shippingAddress->setExtensionAttributes($extensionAttributes);

            // Append pickup ID to address suffix
            $this->appendToAddressSuffix($shippingAddress, $pickupId);

            $this->logger->info(
                sprintf(
                    'DpdIntegrator: Successfully set DPD pickup point "%s" on quote %s (provider: %s)',
                    $pickupId,
                    $quote->getId() ?? 'new',
                    $providerName
                )
            );

            return true;
        } catch (\Exception $e) {
            $this->logger->error(
                sprintf(
                    'DpdIntegrator: Failed to set DPD pickup point data: %s',
                    $e->getMessage()
                )
            );
            return false;
        }
    }

    /**
     * Set the appropriate pickup extension attribute based on provider type
     *
     * @param mixed $extensionAttributes The shipping address extension attributes
     * @param bool $isSwipbox Whether this is a Swipbox locker
     * @param string $pickupId The DPD pickup point ID
     * @return bool True if the attribute was set, false if the required module is not enabled
     */
    private function applyPickupExtensionAttribute($extensionAttributes, bool $isSwipbox, string $pickupId): bool
    {
        if ($isSwipbox) {
            if (!$this->isSwipboxModuleAvailable()) {
                $this->logger->info(
                    'DpdIntegrator: DpdPoland_Dpdshippingswipbox module is not enabled, skipping Swipbox integration'
                );
                return false;
            }
            $extensionAttributes->setShippingDpdSwipbox($pickupId);
            $this->logger->info(
                sprintf(
                    'DpdIntegrator: Set shippingDpdSwipbox "%s" on shipping address',
                    $pickupId
                )
            );
        } else {
            if (!$this->isPickupModuleAvailable()) {
                $this->logger->info(
                    'DpdIntegrator: DpdPoland_Dpdshippingpickup module is not enabled, skipping DPD pickup integration'
                );
                return false;
            }
            $extensionAttributes->setShippingDpdPickup($pickupId);
            $this->logger->info(
                sprintf(
                    'DpdIntegrator: Set shippingDpdPickup "%s" on shipping address',
                    $pickupId
                )
            );
        }

        return true;
    }

    /**
     * Check if the provider name indicates a Swipbox locker
     *
     * @param string $providerName The normalized provider name
     * @return bool True if this is a Swipbox provider
     */
    private function isSwipboxProvider(string $providerName): bool
    {
        return in_array($providerName, [self::PROVIDER_DPD_SWIPBOX, self::PROVIDER_SWIPBOX], true);
    }

    /**
     * Append the pickup ID to the address suffix
     *
     * Appends the pickup ID in format [DPD_PICKUP:${pickupId}] to the
     * shipping address suffix field.
     *
     * @param \Magento\Quote\Api\Data\AddressInterface $address The shipping address
     * @param string $pickupId The DPD pickup point ID
     * @return void
     */
    private function appendToAddressSuffix($address, string $pickupId): void
    {
        $suffix = sprintf(self::ADDRESS_SUFFIX_FORMAT, $pickupId);
        $currentSuffix = $address->getSuffix();

        if (!empty($currentSuffix)) {
            // Check if the suffix already contains a DPD_PICKUP marker
            if (strpos($currentSuffix, '[DPD_PICKUP:') !== false) {
                // Replace existing DPD_PICKUP marker
                $newSuffix = preg_replace('/\[DPD_PICKUP:[^\]]*\]/', $suffix, $currentSuffix);
            } else {
                // Append to existing suffix
                $newSuffix = $currentSuffix . ' ' . $suffix;
            }
        } else {
            $newSuffix = $suffix;
        }

        $address->setSuffix($newSuffix);

        $this->logger->debug(
            sprintf(
                'DpdIntegrator: Set address suffix to "%s"',
                $newSuffix
            )
        );
    }

    /**
     * Get the provider names this integrator handles
     *
     * @return array List containing 'dpd', 'dpd_swipbox', and 'swipbox'
     */
    public function getProviderNames(): array
    {
        return [self::PROVIDER_DPD, self::PROVIDER_DPD_SWIPBOX, self::PROVIDER_SWIPBOX];
    }
}
