<?php

namespace App\Services;

use App\Enums\OrderDetailStatus;
use App\Enums\OrderStatus;
use App\Enums\ShippingMethod;
use App\Models\Order;
use App\Models\OrderDetail;
use App\Models\RemoteOrderDetail;
use App\Models\OrderStatusCatalog;
use App\Models\User;
use App\Models\Universal\UniversalOrderDetail;
use App\Models\Universal\UniversalOrder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class OrderDetailFulfillmentService
{
    /** @var array<int, string> */
    protected const SYNCABLE_ORDER_FIELDS = [
        'original_order_reference',
        'store_name',
        'channel_id',
        'order_type_id',
        'codClient',
        'customer_name',
        'customer_email',
        'customer_phone',
        'payment',
        'total_productsTI',
        'total_productsTE',
        'total_discounts',
        'total_shipping',
        'total_paidTI',
        'total_paidTE',
        'date_order',
    ];

    /** @return array{checked: int, updated: int, unresolved_count: int, unresolved_products: array<int, string>} */
    public function recoverMissingShippingMethodsForOrder(Order $order, ?User $user): array
    {
        $details = $order->details()
            ->when($user !== null, fn ($query) => $query->visibleToUser($user))
            ->where(function ($query): void {
                $query->whereNull('shipping_method')
                    ->orWhere('shipping_method', '');
            })
            ->get();

        if ($details->isEmpty()) {
            return [
                'checked' => 0,
                'updated' => 0,
                'unresolved_count' => 0,
                'unresolved_products' => [],
            ];
        }

        $lookupReferences = collect([
            (string) ($order->original_order_reference ?? ''),
            (string) ($order->order_reference ?? ''),
        ])
            ->map(fn (string $reference): string => trim($reference))
            ->filter()
            ->unique()
            ->values();

        $productReferences = $details
            ->pluck('product_reference')
            ->map(fn ($reference): string => trim((string) $reference))
            ->filter()
            ->unique()
            ->values();

        $remoteDetails = $lookupReferences->isEmpty() || $productReferences->isEmpty()
            ? collect()
            : RemoteOrderDetail::query()
                ->whereIn('order_reference', $lookupReferences->all())
                ->whereIn('product_reference', $productReferences->all())
                ->get();

        $remoteDetailsByKey = $remoteDetails
            ->filter(fn ($row): bool => filled($this->resolveShippingMethodFromSource($row)))
            ->groupBy(fn ($row): string => $this->remoteDetailLookupKey(
                (string) ($row->order_reference ?? ''),
                (string) ($row->product_reference ?? $row->reference_product ?? ''),
            ));

        $unresolvedProductReferences = [];
        $updated = 0;

        foreach ($details as $detail) {
            $shippingMethod = $this->resolveShippingMethodFromRemoteSources(
                $detail,
                $lookupReferences->all(),
                $remoteDetailsByKey,
            );

            if ($shippingMethod === null) {
                $unresolvedProductReferences[] = $detail->product_name
                    ?: $detail->product_reference
                    ?: '#'.$detail->getKey();

                continue;
            }

            $detail->forceFill([
                'shipping_method' => $shippingMethod,
            ]);

            if ($detail->isDirty('shipping_method')) {
                $detail->save();
                $updated++;
            }
        }

        return [
            'checked' => $details->count(),
            'updated' => $updated,
            'unresolved_count' => count($unresolvedProductReferences),
            'unresolved_products' => array_values(array_unique($unresolvedProductReferences)),
        ];
    }

    public function ensureLocalDetailsExist(Order $order): int
    {
        if ($order->details()->exists()) {
            return 0;
        }

        return DB::transaction(function () use ($order): int {
            $lockedOrder = Order::query()
                ->whereKey($order->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            if ($lockedOrder->details()->exists()) {
                return 0;
            }

            $lookupReference = (string) ($lockedOrder->original_order_reference ?: $lockedOrder->order_reference);
            if ($lookupReference === '') {
                Log::warning('ensureLocalDetailsExist skipped: missing order reference', [
                    'order_id' => $lockedOrder->getKey(),
                    'order_reference' => $lockedOrder->order_reference,
                ]);

                return 0;
            }

            $universalOrder = UniversalOrder::query()
                ->where('order_reference', $lookupReference)
                ->first();

            if (! $universalOrder) {
                Log::warning('ensureLocalDetailsExist skipped: universal order not found', [
                    'order_id' => $lockedOrder->getKey(),
                    'order_reference' => $lockedOrder->order_reference,
                    'lookup_reference' => $lookupReference,
                ]);

                return 0;
            }

            $rows = $universalOrder->details()->get();
            $targetSucursal = (int) ($lockedOrder->store?->universal_sucursal_id ?? 0);

            if ($targetSucursal > 0 && filled($lockedOrder->original_order_reference)) {
                $rows = $rows
                    ->filter(fn ($row): bool => (int) ($row->idSucursal ?? 0) === $targetSucursal)
                    ->values();
            }

            $payload = $rows
                ->map(function ($row) use ($lockedOrder, $targetSucursal): array {
                    $qty = max(0, (int) ($row->product_quantity ?? 0));
                    $priceTI = (float) ($row->product_priceTI ?? 0);
                    $priceTE = (float) ($row->product_priceTE ?? 0);
                    $warehouseAssigned = (int) ($row->idSucursal ?? 0);

                    return [
                        'order_reference' => $lockedOrder->order_reference,
                        'product_reference' => (string) ($row->product_reference ?? $row->reference_product ?? ''),
                        'product_name' => (string) ($row->product_name ?? $row->name_product ?? ''),
                        'product_quantity' => $qty,
                        'product_tax' => (float) ($row->product_tax ?? 0),
                        'product_priceTI' => $priceTI,
                        'product_priceTE' => $priceTE,
                        'total_priceTI' => (float) ($row->total_priceTI ?? ($qty * $priceTI)),
                        'total_priceTE' => (float) ($row->total_priceTE ?? ($qty * $priceTE)),
                        'warehouse_assigned' => ($warehouseAssigned > 0 ? (string) $warehouseAssigned : ($targetSucursal > 0 ? (string) $targetSucursal : null)),
                        'shipping_method' => $this->resolveShippingMethodFromSource($row),
                        'current_status' => OrderDetailStatus::Empacado->value,
                        'is_picked' => false,
                    ];
                })
                ->filter(fn (array $detail): bool => filled($detail['product_reference']) || filled($detail['product_name']))
                ->values()
                ->all();

            if ($payload === []) {
                Log::info('ensureLocalDetailsExist resolved no payload', [
                    'order_id' => $lockedOrder->getKey(),
                    'order_reference' => $lockedOrder->order_reference,
                    'lookup_reference' => $lookupReference,
                    'target_sucursal' => $targetSucursal,
                ]);

                return 0;
            }

            $lockedOrder->details()->createMany($payload);

            Log::info('ensureLocalDetailsExist created local details', [
                'order_id' => $lockedOrder->getKey(),
                'order_reference' => $lockedOrder->order_reference,
                'lookup_reference' => $lookupReference,
                'created_count' => count($payload),
                'target_sucursal' => $targetSucursal,
            ]);

            return count($payload);
        });
    }

    public function syncOrderSnapshotFromUniversal(Order $order): int
    {
        Log::info('syncOrderSnapshotFromUniversal invoked', [
            'order_id' => $order->getKey(),
            'order_reference' => $order->order_reference,
            'original_order_reference' => $order->original_order_reference,
        ]);

        return DB::transaction(function () use ($order): int {
            $lockedOrder = Order::query()
                ->with('store')
                ->whereKey($order->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            $universalOrder = $this->resolveUniversalOrder($lockedOrder);

            if (! $universalOrder) {
                Log::warning('syncOrderSnapshotFromUniversal skipped: universal order not found', [
                    'order_id' => $lockedOrder->getKey(),
                    'order_reference' => $lockedOrder->order_reference,
                    'original_order_reference' => $lockedOrder->original_order_reference,
                ]);

                return 0;
            }

            $changes = 0;

            if ($this->syncLocalOrderFromUniversal($lockedOrder, $universalOrder)) {
                $changes++;
            }

            $changes += $this->syncLocalOrderDetailsFromUniversal($lockedOrder, $universalOrder);

            Log::info('syncOrderSnapshotFromUniversal completed', [
                'order_id' => $lockedOrder->getKey(),
                'order_reference' => $lockedOrder->order_reference,
                'original_order_reference' => $lockedOrder->original_order_reference,
                'changes' => $changes,
                'store_id' => $lockedOrder->store?->getKey(),
                'store_universal_sucursal_id' => $lockedOrder->store?->universal_sucursal_id,
            ]);

            return $changes;
        });
    }

    protected function resolveUniversalOrder(Order $order): ?UniversalOrder
    {
        $lookupReference = trim((string) ($order->original_order_reference ?: $order->order_reference));

        if ($lookupReference === '') {
            return null;
        }

        return UniversalOrder::query()
            ->where('order_reference', $lookupReference)
            ->first();
    }

    protected function syncLocalOrderFromUniversal(Order $order, UniversalOrder $universalOrder): bool
    {
        $payload = [];

        foreach (self::SYNCABLE_ORDER_FIELDS as $field) {
            $value = $universalOrder->{$field} ?? null;

            if ($this->shouldSyncValue($value)) {
                $payload[$field] = $value;
            }
        }

        $completo = $universalOrder->completo ?? null;
        if ($completo !== null) {
            $payload['Completo_OFP'] = (bool) $completo;
        }

        if ($payload === []) {
            return false;
        }

        $order->forceFill($payload);

        if (! $order->isDirty()) {
            return false;
        }

        $order->save();

        return true;
    }

    protected function syncLocalOrderDetailsFromUniversal(Order $order, UniversalOrder $universalOrder): int
    {
        $rows = $universalOrder->details()->get();
        $targetSucursal = (int) ($order->store?->universal_sucursal_id ?? 0);

        if ($targetSucursal > 0 && filled($order->original_order_reference)) {
            $rows = $rows
                ->filter(fn ($row): bool => (int) ($row->idSucursal ?? 0) === $targetSucursal)
                ->values();
        }

        /** @var Collection<string, Collection<int, OrderDetail>> $existingDetails */
        $existingDetails = $order->details()
            ->lockForUpdate()
            ->get()
            ->groupBy(fn (OrderDetail $detail): string => $this->detailSyncKey(
                $detail->product_reference,
                $detail->warehouse_assigned,
                $detail->product_name,
            ));

        $updated = 0;

        foreach ($rows as $row) {
            $payload = $this->mapUniversalDetailToLocalPayload($order, $row, $targetSucursal);

            if ($payload === null) {
                continue;
            }

            $key = $this->detailSyncKey(
                $payload['product_reference'] ?? null,
                $payload['warehouse_assigned'] ?? null,
                $payload['product_name'] ?? null,
            );

            /** @var Collection<int, OrderDetail> $matches */
            $matches = $existingDetails->get($key, collect());
            $detail = $matches->shift();
            $existingDetails->put($key, $matches);

            if ($detail) {
                $detail->forceFill($payload);

                if ($detail->isDirty()) {
                    $detail->save();
                    $updated++;
                }

                continue;
            }

            $order->details()->create($payload + [
                'current_status' => OrderDetailStatus::Empacado->value,
                'is_picked' => false,
            ]);
            $updated++;
        }

        return $updated;
    }

    protected function mapUniversalDetailToLocalPayload(Order $order, object $row, int $targetSucursal = 0): ?array
    {
        $qty = max(0, (int) ($row->product_quantity ?? 0));
        $priceTI = (float) ($row->product_priceTI ?? 0);
        $priceTE = (float) ($row->product_priceTE ?? 0);
        $warehouseAssigned = (int) ($row->idSucursal ?? 0);
        $productReference = trim((string) ($row->product_reference ?? $row->reference_product ?? ''));
        $productName = trim((string) ($row->product_name ?? $row->name_product ?? ''));

        if ($productReference === '' && $productName === '') {
            return null;
        }

        return [
            'product_remote_id' => $row->id_product ?? null,
            'order_reference' => $order->order_reference,
            'product_reference' => $productReference,
            'product_name' => $productName,
            'product_quantity' => $qty,
            'product_tax' => (float) ($row->product_tax ?? 0),
            'product_priceTI' => $priceTI,
            'product_priceTE' => $priceTE,
            'total_priceTI' => (float) ($row->total_priceTI ?? ($qty * $priceTI)),
            'total_priceTE' => (float) ($row->total_priceTE ?? ($qty * $priceTE)),
            'warehouse_assigned' => ($warehouseAssigned > 0 ? (string) $warehouseAssigned : ($targetSucursal > 0 ? (string) $targetSucursal : null)),
            'shipping_method' => $this->resolveShippingMethodFromSource($row),
        ];
    }

    protected function detailSyncKey(mixed $productReference, mixed $warehouseAssigned, mixed $productName): string
    {
        return strtolower(trim((string) $productReference))
            .'|'.trim((string) $warehouseAssigned)
            .'|'.strtolower(trim((string) $productName));
    }

    protected function shouldSyncValue(mixed $value): bool
    {
        if ($value === null) {
            return false;
        }

        return ! is_string($value) || trim($value) !== '';
    }

    public function approvePickingForUser(Order $order, array $payload, User $user): int
    {
        return DB::transaction(function () use ($order, $payload, $user): int {
            $details = $order->details()
                ->visibleToUser($user)
                ->lockForUpdate()
                ->get();

            $detailsToApprove = $details->filter(function (OrderDetail $detail) use ($payload): bool {
                $checkboxName = 'picked_' . $detail->getKey();

                return ! (bool) $detail->is_picked && (bool) ($payload[$checkboxName] ?? false);
            });

            $detailWithoutShippingMethod = $detailsToApprove->first(fn (OrderDetail $detail): bool => $detail->shippingMethod() === null);

            if ($detailWithoutShippingMethod) {
                throw ValidationException::withMessages([
                    'shipping_method' => sprintf(
                        'El producto %s de la orden %s no tiene método de envío configurado. Configura shipping_method antes de aprobar el picking.',
                        $detailWithoutShippingMethod->product_name ?: $detailWithoutShippingMethod->product_reference ?: '#'.$detailWithoutShippingMethod->getKey(),
                        $detailWithoutShippingMethod->order_reference,
                    ),
                ]);
            }

            Log::info('approvePickingForUser start', [
                'order_reference' => $order->order_reference,
                'user_id' => $user->getKey(),
                'visible_detail_ids' => $details->pluck('id_order_detail')->all(),
                'visible_detail_checks' => $details->map(fn (OrderDetail $detail): array => [
                    'id_order_detail' => $detail->id_order_detail,
                    'checkbox_name' => 'picked_' . $detail->getKey(),
                    'db_is_picked' => (bool) $detail->is_picked,
                    'payload_value' => $payload['picked_' . $detail->getKey()] ?? null,
                    'warehouse_assigned' => $detail->warehouse_assigned,
                    'current_status' => $detail->current_status,
                ])->all(),
            ]);

            $updated = 0;

            foreach ($details as $detail) {
                $checkboxName = 'picked_' . $detail->getKey();
                $picked = (bool) ($payload[$checkboxName] ?? false);

                Log::info('approvePickingForUser detail evaluation', [
                    'order_reference' => $order->order_reference,
                    'user_id' => $user->getKey(),
                    'id_order_detail' => $detail->id_order_detail,
                    'checkbox_name' => $checkboxName,
                    'db_is_picked_before' => (bool) $detail->is_picked,
                    'payload_value_raw' => $payload[$checkboxName] ?? null,
                    'picked_casted' => $picked,
                ]);

                if ((bool) $detail->is_picked === $picked) {
                    continue;
                }

                $detail->forceFill([
                    'is_picked' => $picked,
                ])->save();

                $this->transitionDetailStatus(
                    $detail->fresh(),
                    $picked ? $this->resolveStatusForPickedDetail($detail) : OrderDetailStatus::Empacado,
                    $picked ? $this->pickingApprovalNoteForDetail($detail) : 'Picking revertido para el producto.',
                    $user,
                );

                Log::info('approvePickingForUser detail saved', [
                    'order_reference' => $order->order_reference,
                    'user_id' => $user->getKey(),
                    'id_order_detail' => $detail->id_order_detail,
                    'db_is_picked_after' => $picked,
                ]);

                $updated++;
            }

            $this->syncOrderPickingApproval($order->fresh(['details', 'statusCatalog']), $user);
            $this->syncOrderStatusFromDetails($order->fresh(['details', 'statusCatalog']), $user);

            Log::info('approvePickingForUser end', [
                'order_reference' => $order->order_reference,
                'user_id' => $user->getKey(),
                'updated_count' => $updated,
            ]);

            return $updated;
        });
    }

    public function moveAuthorizedOrderDetailsToStatus(Order $order, OrderDetailStatus $status, User $user, ?string $note = null): int
    {
        return DB::transaction(function () use ($order, $status, $user, $note): int {
            $details = $order->details()
                ->visibleToUser($user)
                ->where('is_picked', true)
                ->lockForUpdate()
                ->get();

            $updated = 0;

            foreach ($details as $detail) {
                if ($this->transitionDetailStatus($detail, $status, $note, $user)) {
                    $updated++;
                }
            }

            $this->syncOrderStatusFromDetails($order->fresh(['details', 'statusCatalog']), $user);

            return $updated;
        });
    }

    public function markStorePickupGroupDelivered(string $orderReference, ?string $warehouseAssigned, ?User $user, ?string $pickupName = null): int
    {
        if (! $user) {
            return 0;
        }

        return DB::transaction(function () use ($orderReference, $warehouseAssigned, $user, $pickupName): int {
            $details = OrderDetail::query()
                ->visibleToUser($user)
                ->where('order_reference', $orderReference)
                ->where('current_status', OrderDetailStatus::ListoParaRetirar->value)
                ->when(
                    filled($warehouseAssigned),
                    fn ($query) => $query->where('warehouse_assigned', $warehouseAssigned),
                    fn ($query) => $query->whereNull('warehouse_assigned'),
                )
                ->lockForUpdate()
                ->get();

            $updated = 0;
            $note = 'Retiro confirmado en tienda. Retira: '.($pickupName ?? '');

            foreach ($details as $detail) {
                if ($this->transitionDetailStatus($detail, OrderDetailStatus::EntregadoAlCliente, $note, $user)) {
                    $updated++;
                }
            }

            return $updated;
        });
    }

    public function markReadyToShipGroupDelivered(string $orderReference, ?string $warehouseAssigned, ?User $user): int
    {
        if (! $user) {
            return 0;
        }

        return DB::transaction(function () use ($orderReference, $warehouseAssigned, $user): int {
            $details = OrderDetail::query()
                ->visibleToUser($user)
                ->where('order_reference', $orderReference)
                ->where('current_status', OrderDetailStatus::ListoParaEnviar->value)
                ->when(
                    filled($warehouseAssigned),
                    fn ($query) => $query->where('warehouse_assigned', $warehouseAssigned),
                    fn ($query) => $query->whereNull('warehouse_assigned'),
                )
                ->lockForUpdate()
                ->get();

            $updated = 0;
            $note = 'Productos entregados al transporte desde el módulo de envío.';

            foreach ($details as $detail) {
                if ($this->transitionDetailStatus($detail, OrderDetailStatus::EntregadoAlTransporte, $note, $user)) {
                    $updated++;
                }
            }

            if ($details->isNotEmpty()) {
                $order = $details->first()?->order;

                if ($order) {
                    $this->syncOrderStatusFromDetails($order->fresh(['details', 'statusCatalog']), $user);
                }
            }

            return $updated;
        });
    }

    public function transitionDetailStatus(OrderDetail $detail, OrderDetailStatus|string $status, ?string $note = null, ?User $user = null): bool
    {
        $target = $status instanceof OrderDetailStatus
            ? $status
            : OrderDetailStatus::from($status);

        $current = $detail->detailStatus();
        if ($current === $target) {
            return false;
        }

        $detail->forceFill([
            'current_status' => $target->value,
        ])->save();

        $detail->statusLogs()->create([
            'order_reference' => $detail->order_reference,
            'status' => $target->value,
            'note' => $note,
            'user_id' => $user?->getKey(),
            'happened_at' => now(),
        ]);

        if ($detail->relationLoaded('order') || $detail->order()->exists()) {
            $this->syncOrderStatusFromDetails($detail->order()->firstOrFail(), $user);
        }

        return true;
    }

    public function syncOrderPickingApproval(Order $order, ?User $user = null): bool
    {
        return DB::transaction(function () use ($order, $user): bool {
            $lockedOrder = Order::query()
                ->whereKey($order->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            /** @var EloquentCollection<int, OrderDetail> $details */
            $details = $lockedOrder->details()
                ->lockForUpdate()
                ->get();

            $terminalStatuses = OrderDetailStatus::terminalValues();
            $relevantDetails = $details->reject(
                fn (OrderDetail $detail): bool => in_array((string) ($detail->current_status ?? ''), $terminalStatuses, true),
            );

            $allPicked = $relevantDetails->isEmpty()
                || $relevantDetails->every(fn (OrderDetail $detail): bool => (bool) $detail->is_picked);

            $dirty = false;
            if ((bool) $lockedOrder->picking_aprobado !== $allPicked) {
                $lockedOrder->picking_aprobado = $allPicked;
                $dirty = true;
            }

            if ($dirty) {
                $lockedOrder->save();
            }

            return $allPicked;
        });
    }

    public function syncOrderStatusFromDetails(Order $order, ?User $user = null): void
    {
        DB::transaction(function () use ($order, $user): void {
            $lockedOrder = Order::query()
                ->with('statusCatalog')
                ->whereKey($order->getKey())
                ->lockForUpdate()
                ->firstOrFail();

            /** @var EloquentCollection<int, OrderDetail> $details */
            $details = $lockedOrder->details()
                ->lockForUpdate()
                ->get(['id_order_detail', 'order_reference', 'current_status', 'is_picked']);

            if ($details->isEmpty()) {
                return;
            }

            if ($details->contains(fn (OrderDetail $detail): bool => (string) $detail->order_reference !== (string) $lockedOrder->order_reference)) {
                throw ValidationException::withMessages([
                    'order_reference' => 'Se detectaron productos que no pertenecen a la orden que se intenta sincronizar.',
                ]);
            }

            $target = $this->resolveOrderStatusFromDetailSnapshot($lockedOrder, $details);
            $targetId = $target ? $this->orderStatusId($target) : null;
            $shouldMarkComplete = $details->every(
                fn (OrderDetail $detail): bool => (string) $detail->current_status === OrderDetailStatus::EntregadoAlCliente->value,
            );

            $previousStatusId = (int) $lockedOrder->status_id;
            $statusChanged = $targetId !== null && $previousStatusId !== $targetId;
            $completeChanged = (bool) $lockedOrder->Completo_OFP !== $shouldMarkComplete;

            if (! $statusChanged && ! $completeChanged) {
                return;
            }

            if ($statusChanged) {
                $lockedOrder->status_id = $targetId;
            }

            if ($completeChanged) {
                $lockedOrder->Completo_OFP = $shouldMarkComplete;
            }

            $lockedOrder->save();

            if ($statusChanged && $target !== null) {
                $lockedOrder->statusLogs()->create([
                    'status' => $target->value,
                    'note' => 'Estado de la orden sincronizado desde estados de productos.',
                    'user_id' => $user?->getKey(),
                    'status_id_old' => $previousStatusId > 0 ? $previousStatusId : null,
                    'status_id_new' => $targetId,
                    'happened_at' => now(),
                ]);
            }
        });
    }

    /** @param EloquentCollection<int, OrderDetail> $details */
    private function resolveOrderStatusFromDetailSnapshot(Order $order, EloquentCollection $details): ?OrderStatus
    {
        $statuses = $details
            ->pluck('current_status')
            ->map(fn ($status): string => trim((string) $status))
            ->filter(fn (string $status): bool => $status !== '')
            ->unique()
            ->values();

        if ($statuses->isEmpty()) {
            return $this->fallbackOrderStatus($order, $details);
        }

        $allDelivered = $details->every(
            fn (OrderDetail $detail): bool => (string) $detail->current_status === OrderDetailStatus::EntregadoAlCliente->value,
        );

        if ($allDelivered) {
            return OrderStatus::EntregadoAlCliente;
        }

        if ($statuses->contains(OrderDetailStatus::EntregadoAlCliente->value)) {
            return OrderStatus::EntregadoParcial;
        }

        if ($statuses->contains(OrderDetailStatus::EntregadoAlTransporte->value)) {
            return OrderStatus::EntregadoAlTransporte;
        }

        $terminalStatuses = OrderDetailStatus::terminalValues();
        $hasPendingPicking = $details->contains(function (OrderDetail $detail) use ($terminalStatuses): bool {
            $currentStatus = trim((string) ($detail->current_status ?? ''));

            if (in_array($currentStatus, $terminalStatuses, true)) {
                return false;
            }

            return ! (bool) $detail->is_picked;
        });

        if ($hasPendingPicking) {
            $current = $this->resolveOrderStatusEnum((string) ($order->statusCatalog?->code ?? ''));

            return match ($current) {
                OrderStatus::OnHold => OrderStatus::OnHold,
                OrderStatus::Empacado => OrderStatus::Empacado,
                default => OrderStatus::Preparando,
            };
        }

        $hasReadyToShip = $statuses->contains(OrderDetailStatus::ListoParaEnviar->value);
        $hasReadyForUber = $statuses->contains(OrderDetailStatus::ListoParaUber->value)
            || $statuses->contains(OrderDetailStatus::EnvioPorUber->value);
        $hasReadyToPickup = $statuses->contains(OrderDetailStatus::ListoParaRetirar->value);

        if (count(array_filter([$hasReadyToShip, $hasReadyForUber, $hasReadyToPickup])) > 1) {
            return OrderStatus::DespachoMulticanal;
        }

        if ($hasReadyForUber) {
            return OrderStatus::ListoParaUber;
        }

        if ($hasReadyToShip) {
            return OrderStatus::ListoParaEnviar;
        }

        if ($hasReadyToPickup) {
            return OrderStatus::ListoParaRetirar;
        }

        if ($statuses->every(fn (string $status): bool => $status === OrderDetailStatus::Cancelado->value)) {
            return OrderStatus::Cancelado;
        }

        if (
            $statuses->contains(OrderDetailStatus::Empacado->value)
            || $statuses->contains(OrderDetailStatus::PickingCompleto->value)
            || $details->every(fn (OrderDetail $detail): bool => (bool) $detail->is_picked)
        ) {
            return OrderStatus::Empacado;
        }

        return $this->fallbackOrderStatus($order, $details);
    }

    /** @param EloquentCollection<int, OrderDetail> $details */
    private function fallbackOrderStatus(Order $order, EloquentCollection $details): ?OrderStatus
    {
        if ($details->every(fn (OrderDetail $detail): bool => (bool) $detail->is_picked)) {
            return OrderStatus::Empacado;
        }

        $current = $this->resolveOrderStatusEnum((string) ($order->statusCatalog?->code ?? ''));

        return match ($current) {
            OrderStatus::Ingresado,
            OrderStatus::Preparando,
            OrderStatus::OnHold => $current,
            default => null,
        };
    }

    private function orderStatusId(OrderStatus $status): ?int
    {
        $value = OrderStatusCatalog::query()
            ->where(function ($query) use ($status): void {
                $query->whereIn('code', $this->possibleOrderStatusCodes($status))
                    ->orWhereIn('name', $this->possibleOrderStatusNames($status));
            })
            ->orderByRaw('case when code = ? then 0 when name = ? then 1 else 2 end', [$status->value, $status->label()])
            ->value('id_order_status');

        return $value !== null ? (int) $value : null;
    }

    private function resolveOrderStatusEnum(string $code): ?OrderStatus
    {
        $normalized = trim($code);

        return match ($normalized) {
            'ListoEnvio' => OrderStatus::ListoParaEnviar,
            'ListoRetiro' => OrderStatus::ListoParaRetirar,
            'Enviado' => OrderStatus::EntregadoAlTransporte,
            'Retirado' => OrderStatus::EntregadoAlCliente,
            'OnHold' => OrderStatus::OnHold,
            'Redespacho' => OrderStatus::Redespacho,
            default => OrderStatus::tryFrom($normalized),
        };
    }

    /** @return array<int, string> */
    private function possibleOrderStatusCodes(OrderStatus $status): array
    {
        return match ($status) {
            OrderStatus::ListoParaEnviar => [$status->value, 'ListoEnvio'],
            OrderStatus::ListoParaRetirar => [$status->value, 'ListoRetiro'],
            OrderStatus::EntregadoAlTransporte => [$status->value, 'Enviado'],
            OrderStatus::EntregadoAlCliente => [$status->value, 'Retirado'],
            OrderStatus::OnHold => [$status->value, 'OnHold'],
            OrderStatus::Redespacho => [$status->value, 'Redespacho'],
            default => [$status->value],
        };
    }

    /** @return array<int, string> */
    private function possibleOrderStatusNames(OrderStatus $status): array
    {
        return array_values(array_unique(array_filter([
            $status->label(),
            match ($status) {
                OrderStatus::ListoParaEnviar => 'Listo para enviar',
                OrderStatus::ListoParaRetirar => 'Listo para retirar',
                OrderStatus::EntregadoAlTransporte => 'Entregado al transporte',
                OrderStatus::EntregadoAlCliente => 'Entregado al cliente',
                default => null,
            },
        ])));
    }

    protected function resolveStatusForPickedDetail(OrderDetail $detail): OrderDetailStatus
    {
        return $detail->shippingMethod()?->detailStatus()
            ?? throw ValidationException::withMessages([
                'shipping_method' => sprintf(
                    'El producto %s no tiene un método de envío válido configurado.',
                    $detail->product_name ?: $detail->product_reference ?: '#'.$detail->getKey(),
                ),
            ]);
    }

    protected function pickingApprovalNoteForDetail(OrderDetail $detail): string
    {
        return match ($this->resolveStatusForPickedDetail($detail)) {
            OrderDetailStatus::ListoParaEnviar => 'Picking aprobado. Producto movido automáticamente a Listo para enviar.',
            OrderDetailStatus::ListoParaUber => 'Picking aprobado. Producto movido automáticamente a Listo para el Uber.',
            OrderDetailStatus::ListoParaRetirar => 'Picking aprobado. Producto movido automáticamente a Listo para retirar.',
            OrderDetailStatus::EnvioPorUber => 'Picking aprobado. Producto movido automáticamente a Envío por Uber.',
            default => 'Picking aprobado para el producto.',
        };
    }

    protected function resolveShippingMethodFromSource(object ...$sources): ?string
    {
        $candidateFields = [
            'shipping_method',
            'shippingMethod',
            'metodo_envio',
            'metodoEnvio',
            'delivery_method',
            'deliveryMethod',
            'shipping_type',
            'shippingType',
            'delivery_type',
            'deliveryType',
            'dispatch_method',
            'dispatchMethod',
            'tipo_envio',
            'tipoEnvio',
            'carrier',
        ];

        foreach ($sources as $source) {
            if (! $source) {
                continue;
            }

            foreach ($candidateFields as $field) {
                $rawValue = data_get($source, $field);

                if (! is_string($rawValue) || trim($rawValue) === '') {
                    continue;
                }

                $normalized = $this->normalizeShippingMethod($rawValue);

                if ($normalized !== null) {
                    return $normalized;
                }
            }
        }

        return null;
    }

    protected function normalizeShippingMethod(string $value): ?string
    {
        $normalized = Str::of($value)
            ->ascii()
            ->lower()
            ->replace(['-', '_'], ' ')
            ->squish()
            ->value();

        return match (true) {
            str_contains($normalized, 'uber') => ShippingMethod::Uber->value,
            str_contains($normalized, 'tienda'),
            str_contains($normalized, 'pickup'),
            str_contains($normalized, 'retiro') => ShippingMethod::Tienda->value,
            str_contains($normalized, 'envio'),
            str_contains($normalized, 'shipping'),
            str_contains($normalized, 'delivery'),
            str_contains($normalized, 'despacho') => ShippingMethod::Envio->value,
            default => null,
        };
    }

    protected function resolveShippingMethodFromRemoteSources(OrderDetail $detail, array $lookupReferences, Collection $remoteDetailsByKey): ?string
    {
        $productReference = trim((string) ($detail->product_reference ?? ''));

        if ($productReference === '') {
            return null;
        }

        foreach ($lookupReferences as $lookupReference) {
            $remoteKey = $this->remoteDetailLookupKey($lookupReference, $productReference);
            $remoteRow = $remoteDetailsByKey->get($remoteKey)?->first();

            if ($remoteRow) {
                $shippingMethod = $this->resolveShippingMethodFromSource($remoteRow);

                if ($shippingMethod !== null) {
                    return $shippingMethod;
                }
            }
        }

        foreach ($lookupReferences as $lookupReference) {
            $universalRow = UniversalOrderDetail::query()
                ->where('order_reference', $lookupReference)
                ->where('product_reference', $productReference)
                ->first();

            if (! $universalRow) {
                continue;
            }

            $shippingMethod = $this->resolveShippingMethodFromSource($universalRow);

            if ($shippingMethod !== null) {
                return $shippingMethod;
            }
        }

        return null;
    }

    protected function remoteDetailLookupKey(string $orderReference, string $productReference): string
    {
        return trim($orderReference).'|'.strtolower(trim($productReference));
    }
}