<?php

namespace App\Services\Shipment;

use App\Models\Order;
use App\Models\Shipment;
use App\Models\StockMovement;
use App\Services\InventoryService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ShipmentStatusSyncService
{
    public function __construct(
        private readonly ShipmentOrderStatusMapper $mapper,
        private readonly InventoryService $inventoryService
    ) {}

    /**
     * @param  array<string, mixed>  $context
     */
    public function applyCourierStatus(Shipment $shipment, string $courierStatus, array $context = []): void
    {
        $trimmedStatus = trim($courierStatus);

        $metadata = $shipment->metadata ?? [];
        if (!is_array($metadata)) {
            $metadata = [];
        }

        $trackingSync = $metadata['tracking_sync'] ?? [];
        if (!is_array($trackingSync)) {
            $trackingSync = [];
        }

        $metadata['tracking_sync'] = array_merge($trackingSync, $context, [
            'external_status' => $context['external_status'] ?? $trimmedStatus,
            'synced_at' => now()->toDateTimeString(),
        ]);

        $shipment->update([
            'status' => $trimmedStatus,
            'metadata' => $metadata,
        ]);

        $mappedOrderStatus = $this->mapper->toOrderStatus($trimmedStatus);

        if ($mappedOrderStatus === null) {
            $shipment->loadMissing('carrier', 'order');

            Log::warning('Unmapped shipment status', [
                'shipment_id' => $shipment->id,
                'order_id' => $shipment->order_id,
                'raw_status' => $trimmedStatus,
                'carrier_slug' => $shipment->carrier?->slug,
            ]);

            return;
        }

        $shipment->loadMissing('order');

        if ($shipment->order) {
            $this->syncOrderFromShipments($shipment->order);
        }
    }

    /**
     * @return array{derived: string, next: string, would_change: bool}|null
     */
    public function resolveOrderStatusFromShipments(Order $order): ?array
    {
        $order->loadMissing('shipments');

        $mappedStatuses = [];

        foreach ($order->shipments as $shipment) {
            $mapped = $this->mapper->toOrderStatus($shipment->status);

            if ($mapped !== null) {
                $mappedStatuses[] = $mapped;
            }
        }

        if ($mappedStatuses === []) {
            return null;
        }

        $derivedStatus = $this->pickHighestPriorityStatus($mappedStatuses);
        $nextStatus = $this->resolveNextOrderStatus($order->status, $derivedStatus);

        return [
            'derived' => $derivedStatus,
            'next' => $nextStatus,
            'would_change' => $nextStatus !== $order->status,
        ];
    }

    public function syncOrderFromShipments(Order $order): bool
    {
        $resolution = $this->resolveOrderStatusFromShipments($order);
        $statusChanged = false;

        if ($resolution !== null && $resolution['would_change']) {
            $order->update(['status' => $resolution['next']]);
            $statusChanged = true;
        }

        $this->restockOrderIfAnyShipmentRtoDelivered($order);

        return $statusChanged;
    }

    public function orderHasRtoDeliveredShipment(Order $order): bool
    {
        $order->loadMissing('shipments');

        foreach ($order->shipments as $shipment) {
            if ($this->mapper->isRtoDelivered($shipment->status)) {
                return true;
            }
        }

        return false;
    }

    private function restockOrderIfAnyShipmentRtoDelivered(Order $order): bool
    {
        if (!$this->orderHasRtoDeliveredShipment($order)) {
            return false;
        }

        if (!$this->inventoryService->orderHasPendingRestock($order)) {
            return false;
        }

        $beforeCount = $this->countRestockMovementsForOrder($order->id);

        DB::transaction(function () use ($order): void {
            $this->inventoryService->restockOrder($order->fresh('items'), 'returned');
        });

        return $this->countRestockMovementsForOrder($order->id) > $beforeCount;
    }

    private function countRestockMovementsForOrder(int $orderId): int
    {
        return StockMovement::query()
            ->where('type', StockMovement::TYPE_RESTOCK)
            ->where('notes', 'like', "Order #{$orderId}%")
            ->count();
    }

    /**
     * @param  array<int, string>  $statuses
     */
    private function pickHighestPriorityStatus(array $statuses): string
    {
        $priorities = ShipmentOrderStatusMapper::STATUS_PRIORITY;

        usort($statuses, static function (string $a, string $b) use ($priorities): int {
            return ($priorities[$b] ?? 0) <=> ($priorities[$a] ?? 0);
        });

        return $statuses[0];
    }

    private function resolveNextOrderStatus(string $current, string $derived): string
    {
        if ($current === $derived) {
            return $current;
        }

        $priorities = ShipmentOrderStatusMapper::STATUS_PRIORITY;
        $currentPriority = $priorities[$current] ?? 0;
        $derivedPriority = $priorities[$derived] ?? 0;

        if ($current === Order::STATUS_DELIVERED && $derived !== Order::STATUS_DELIVERED) {
            return $current;
        }

        if (in_array($current, [Order::STATUS_CANCELLED, Order::STATUS_RETURNED, Order::STATUS_LOST], true)) {
            if ($derivedPriority > $currentPriority) {
                return $derived;
            }

            return $current;
        }

        if ($derived === Order::STATUS_AWAITING_PICKUP) {
            if (!in_array($current, [
                Order::STATUS_PLACED,
                Order::STATUS_CONFIRMED,
                Order::STATUS_PACKED,
                Order::STATUS_AWAITING_PICKUP,
            ], true)) {
                return $current;
            }
        }

        if ($derived === Order::STATUS_SHIPPED) {
            if (in_array($current, [
                Order::STATUS_AWAITING_PAYMENT,
                Order::STATUS_CANCELLED,
                Order::STATUS_RETURNED,
                Order::STATUS_LOST,
                Order::STATUS_DELIVERED,
            ], true)) {
                return $current;
            }
        }

        if ($derivedPriority < $currentPriority) {
            return $current;
        }

        return $derived;
    }
}
