<?php

declare(strict_types=1);

namespace Autogedal\AwbRetry\Model;

use Eadesigndev\Awb\Model\AwbRepository;
use Eadesigndev\Awb\Model\Sources\CarrierType;
use Leadlion\AutogedalOrderFlow\Helper\Data as OrderFlowData;
use Leadlion\Web2Sms\Helper\Data as SmsHelper;
use Leadlion\AutogedalWarehouses\Model\ResourceModel\Warehouse\CollectionFactory as WarehouseCollectionFactory;
use Magento\Framework\App\ResourceConnection;
use Magento\Sales\Api\ShipmentRepositoryInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Status\HistoryFactory;
use Magento\Sales\Model\Order\Shipment\TrackFactory;
use Psr\Log\LoggerInterface;
class RetryProcessor
{
    public function __construct(
        private readonly AwbErrorLogManager $errorLogManager,
        private readonly CourierResolver $courierResolver,
        private readonly AwbRepository $awbRepository,
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly ShipmentRepositoryInterface $shipmentRepository,
        private readonly TrackFactory $trackFactory,
        private readonly HistoryFactory $historyFactory,
        private readonly OrderFlowData $orderFlowHelper,
        private readonly SmsHelper $smsHelper,
        private readonly PickupNameResolver $pickupNameResolver,
        private readonly WarehouseCollectionFactory $warehouseCollectionFactory,
        private readonly ResourceConnection $resourceConnection,
        private readonly LoggerInterface $logger
    ) {
    }

    public function process(int $errorLogId): string
    {
        $log = $this->errorLogManager->loadById($errorLogId);
        $awbId = (int) $log->getData('awb_id');
        $carrierId = (int) $log->getData('carrier_id');
        $awb = $this->awbRepository->getById($awbId);
        $orderId = (int) ($log->getData('order_id') ?: $awb->getData('order_id'));

        if (!$orderId) {
            throw new \Magento\Framework\Exception\LocalizedException(__('The selected AWB log is missing a linked order.'));
        }

        $order = $this->orderRepository->get($orderId);
        $generator = $this->courierResolver->resolve($carrierId);
        $carrierLabel = CarrierType::getAvailableStatic()[$carrierId] ?? (string) $carrierId;
        $pickupMap = $this->pickupNameResolver->resolve($carrierId);
        $pickupId = (string) $awb->getData('awb_pickup_id');
        $pickupName = $pickupMap[$pickupId] ?? $pickupMap[(int) $pickupId] ?? '';
        if ($pickupName === '') {
            $pickupName = $this->resolveWarehousePickupName($order);
        }
        $awbNumber = trim((string) $awb->getAwbNumber());
        if ($awbNumber === '') {
            $awbNumber = $this->getGeneratedAwbNumber($log) ?? '';
        }
        if ($awbNumber === '') {
            $awbNumber = $generator->generateAwb($awbId);
            $this->errorLogManager->markGenerated($errorLogId, $awbNumber);
        } elseif ($this->getGeneratedAwbNumber($log) !== $awbNumber) {
            // Keep the retry log aligned with the AWB we are reusing.
            $this->errorLogManager->markGenerated($errorLogId, $awbNumber);
        }

        $connection = $this->resourceConnection->getConnection();
        $connection->beginTransaction();

        try {
            $awb->setData('awb_number', $awbNumber);
            $awb->setData('status', 1);
            $this->awbRepository->save($awb);

            $order->setData('awb_number', $awbNumber);
            $order->setData('pickup_id', $pickupId ?: null);
            $order->setData('pickup_name', $pickupName);
            $order->setData('carrier_id', $carrierId);
            $order->setData('carrier_name', $carrierLabel);
            $order->setData('state', Order::STATE_PROCESSING);
            $orderStatus = $this->orderFlowHelper->getAwbStatusCode() ?: $order->getStatus();
            $order->setData('status', $orderStatus);

            $history = $this->historyFactory->create()
                ->setStatus($orderStatus)
                ->setEntityName(Order::ENTITY)
                ->setComment(__('S-a generat AWB %1 nr %2', $carrierLabel, $awbNumber));
            $history->setIsCustomerNotified(false)->setIsVisibleOnFront(false);
            $order->addStatusHistory($history);
            $this->orderRepository->save($order);

            $this->errorLogManager->markResolved($errorLogId);

            $connection->commit();
        } catch (\Throwable $throwable) {
            $connection->rollBack();
            throw $throwable;
        }

        try {
            $this->syncShipmentTrackAndSms($order, $awbNumber, $carrierLabel);
        } catch (\Throwable $throwable) {
            $this->logger->warning(
                (string) __('AWB retry completed, but shipment track or SMS sync failed for order %1: %2', $orderId, $throwable->getMessage()),
                ['exception' => $throwable, 'order_id' => $orderId, 'awb_number' => $awbNumber]
            );
        }

        return (string) $awbNumber;
    }

    private function syncShipmentTrackAndSms(OrderInterface $order, string $awbNumber, string $carrierLabel): void
    {
        if ($awbNumber === '') {
            return;
        }

        $shipmentId = (int) $order->getData('shipment_id');
        $shipment = null;

        if ($shipmentId > 0) {
            try {
                $shipment = $this->shipmentRepository->get($shipmentId);
            } catch (\Throwable) {
                $shipment = null;
            }
        }

        if ($shipment === null || !$shipment->getId()) {
            $shipment = $order->getShipmentsCollection()->getFirstItem();
        }

        if (!$shipment || !$shipment->getId()) {
            return;
        }

        $existingTrack = null;
        foreach ($shipment->getTracksCollection() as $track) {
            if ((string) $track->getTrackNumber() === $awbNumber) {
                $existingTrack = $track;
                break;
            }
        }

        if ($existingTrack === null) {
            $track = $this->trackFactory->create()
                ->setNumber($awbNumber)
                ->setCarrierCode($carrierLabel)
                ->setTitle($carrierLabel);
            $shipment->addTrack($track);
            $this->shipmentRepository->save($shipment);
        }

        $this->smsHelper->callAfterShipmentSaveByCron($shipment, $awbNumber, $carrierLabel, $carrierLabel);
    }

    private function resolveWarehousePickupName(OrderInterface $order): string
    {
        $warehouseId = (string) $order->getData('autogedal_allocated_warehouse');
        if ($warehouseId === '') {
            return '';
        }

        $warehouse = $this->warehouseCollectionFactory->create()
            ->addFieldToFilter('warehouse_id', $warehouseId)
            ->getFirstItem();

        if (!$warehouse || !$warehouse->getId()) {
            return '';
        }

        return (string) $warehouse->getName();
    }

    private function getGeneratedAwbNumber(\Autogedal\AwbRetry\Model\ErrorLog $log): ?string
    {
        $generatedAwbNumber = trim((string) $log->getData('generated_awb_number'));
        if ($generatedAwbNumber !== '') {
            return $generatedAwbNumber;
        }

        $rawRequestContext = (string) $log->getData('request_context');
        if ($rawRequestContext === '') {
            return null;
        }

        try {
            $requestContext = json_decode($rawRequestContext, true, 512, JSON_THROW_ON_ERROR);
        } catch (\Throwable) {
            return null;
        }

        if (!is_array($requestContext)) {
            return null;
        }

        $generatedAwbNumber = trim((string) ($requestContext['generated_awb_number'] ?? ''));
        return $generatedAwbNumber !== '' ? $generatedAwbNumber : null;
    }

}
