<?php

declare(strict_types=1);

// phpcs:disable Generic.Files.LineLength.TooLong

namespace Macopedia\OrlenShipping\Model\Carrier;

use Macopedia\OrlenShipping\Helper\Data as Helper;
use Macopedia\OrlenShipping\Model\Api\Pack;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Directory\Helper\Data;
use Magento\Directory\Model\CountryFactory;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Directory\Model\RegionFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Xml\Security;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Quote\Model\Quote\Address\RateResult\Error;
use Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory;
use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory;
use Magento\Shipping\Model\Carrier\AbstractCarrierOnline;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Rate\Result;
use Magento\Shipping\Model\Rate\ResultFactory;
use Magento\Shipping\Model\Simplexml\ElementFactory;
use Magento\Shipping\Model\Tracking\Result\StatusFactory;
use Psr\Log\LoggerInterface;

class OrlenShipping extends AbstractCarrierOnline implements CarrierInterface
{
    public const CODE = 'orlenpaczka';

    public const ORLEN_DISABLE_DELIVERY_ATTR = 'orlen_disable_delivery';

    /**
     * @var string
     */
    protected $_code = self::CODE;

    /**
     * @param ScopeConfigInterface $scopeConfig
     * @param ErrorFactory $rateErrorFactory
     * @param LoggerInterface $logger
     * @param Security $xmlSecurity
     * @param ElementFactory $xmlElFactory
     * @param ResultFactory $rateFactory
     * @param MethodFactory $rateMethodFactory
     * @param \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory
     * @param \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory
     * @param StatusFactory $trackStatusFactory
     * @param RegionFactory $regionFactory
     * @param CountryFactory $countryFactory
     * @param CurrencyFactory $currencyFactory
     * @param Data $directoryData
     * @param StockRegistryInterface $stockRegistry
     * @param Pack $pack
     * @param Helper $helper
     * @param array $data
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        ErrorFactory $rateErrorFactory,
        LoggerInterface $logger,
        Security $xmlSecurity,
        ElementFactory $xmlElFactory,
        ResultFactory $rateFactory,
        MethodFactory $rateMethodFactory,
        \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory,
        \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory,
        StatusFactory $trackStatusFactory,
        RegionFactory $regionFactory,
        CountryFactory $countryFactory,
        CurrencyFactory $currencyFactory,
        Data $directoryData,
        StockRegistryInterface $stockRegistry,
        protected readonly Pack $pack,
        protected readonly Helper $helper,
        array $data = []
    ) {
        parent::__construct(
            $scopeConfig,
            $rateErrorFactory,
            $logger,
            $xmlSecurity,
            $xmlElFactory,
            $rateFactory,
            $rateMethodFactory,
            $trackFactory,
            $trackErrorFactory,
            $trackStatusFactory,
            $regionFactory,
            $countryFactory,
            $currencyFactory,
            $directoryData,
            $stockRegistry,
            $data
        );
    }

    /**
     * Collect rates
     *
     * @param RateRequest $request
     * @return bool|DataObject|Result|null
     */
    public function collectRates(RateRequest $request)
    {
        if (!$this->getConfigFlag('active')
            || !$this->getConfigData('map_widget_token')
        ) {
            return false;
        }

        $this->setRawRequest($request);
        $result = $this->_rateFactory->create();
        $this->_result = $result;

        foreach ($request->getAllItems() as $item) {
            if ($item->getProduct()->getData(self::ORLEN_DISABLE_DELIVERY_ATTR)) {
                /** @var Error $error */
                $error = $this->_rateErrorFactory->create();
                $error->setCarrier($this->getCarrierCode());
                $error->setCarrierTitle($this->getConfigData('title'));
                $error->setMethod($this->getCarrierCode());
                $error->setMethodTitle($this->getConfigData('name'));
                $error->setErrorMessage(
                    __(
                        'Unfortunately, ORLEN Paczka delivery is not available for the item "%1" due to size restrictions.',
                        $item->getName()
                    )
                );

                return $error;
            }
        }

        $method = $this->_rateMethodFactory->create();
        $method->setCarrier($this->getCarrierCode());
        $method->setMethod($this->getCarrierCode());
        $method->setCarrierTitle($this->getConfigData('title'));
        $method->setMethodTitle($this->getConfigData('name'));
        $shippingCost = $this->getMethodPrice($this->getConfigData('price'));
        $method->setPrice($shippingCost);
        $method->setCost($shippingCost);

        $result->append($method);

        $this->setPriceForFreeShipping($request);

        return $result;
    }

    /**
     * Set price for free shipping
     *
     * @param RateRequest $request
     * @return void
     */
    protected function setPriceForFreeShipping(RateRequest $request): void
    {
        $freeMethod = $this->getConfigData($this->_freeMethod);
        if ($request->getFreeShipping() && $freeMethod) {
            if ($this->_result) {
                foreach ($this->_result->getAllRates() as $item) {
                    $item->setPrice(0);
                }
            }
        }
    }

    /**
     * Calculate price considering free shipping and handling fee
     *
     * @param string $cost
     * @param string $method
     * @return float|string
     * @api
     */
    public function getMethodPrice($cost, $method = '')
    {
        $discount = 0;

        if ($this->_rawRequest->getPackageValue() != $this->_rawRequest->getPackageValueWithDiscount()) {
            $discount = $this->_rawRequest->getPackageValue() - $this->_rawRequest->getPackageValueWithDiscount();
        }

        return $this->getConfigFlag(
            'free_shipping_enable'
        ) && $this->getConfigData(
            'free_shipping_subtotal'
        ) <= $this->_rawRequest->getBaseSubtotalInclTax() - $discount ? '0.00' : $this->getFinalPriceWithHandlingFee(
            $cost
        );
    }

    /**
     * Get allowed methods
     *
     * @return array
     */
    public function getAllowedMethods(): array
    {
        return [$this->_code => __($this->getConfigData('name'))];
    }

    /**
     * Do shipment request
     *
     * @param DataObject $request
     * @return DataObject
     */
    protected function _doShipmentRequest(DataObject $request): DataObject
    {
        $result = new \Magento\Framework\DataObject();

        try {
            $packLabel = $this->pack->generatePack($request);
            $result->setShippingLabelContent($packLabel->LabelData ?? null);
            $result->setTrackingNumber($packLabel->PackCode_RUCH ?? null);
        } catch (\Exception $e) {
            $this->_logger->error($e->getMessage() . PHP_EOL . $e->getTraceAsString());
            $result->setErrors($e->getMessage());
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function isZipCodeRequired($countryId = null): bool
    {
        return false;
    }

    /**
     * Disable additional validation
     *
     * @param DataObject $request
     * @return bool
     */
    public function processAdditionalValidation(\Magento\Framework\DataObject $request): bool
    {
        return true;
    }

    /**
     * Get tracking
     *
     * @param string $tracking
     * @return \Magento\Shipping\Model\Tracking\Result
     */
    public function getTracking(string $tracking): \Magento\Shipping\Model\Tracking\Result
    {
        $track = $this->_trackFactory->create();
        $status = $this->_trackStatusFactory->create();
        $status->setCarrier($this->_code);
        $status->setCarrierTitle($this->getConfigData('title'));
        $status->setTracking($tracking);
        $status->setPopup(1);
        $status->setUrl(
            $this->helper->getEndpointTracking($tracking)
        );

        $track->append($status);

        return $track;
    }
}
