<?php

namespace App\Modules\Sales\Services;

use App\Models\User;
use App\Modules\Inventory\Stock\Exceptions\InsufficientStockException;
use App\Modules\Inventory\Stock\Support\DecimalQuantity;
use App\Modules\Sales\Exceptions\SalesOperationalException;
use App\Modules\Sales\Models\SalesOrder;
use App\Support\Bulk\BulkOperationResult;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;

final class SalesOrderBulkAllocateMaxService
{
    public function __construct(
        private readonly SalesOrderUiBatchOperationService $batch,
    ) {}

    /**
     * @param  list<int|string>  $orderIds
     */
    public function run(User $user, int $tenantId, array $orderIds): BulkOperationResult
    {
        $orderIds = $this->normalizeIds($orderIds);
        $succeeded = [];
        $failures = [];

        foreach ($orderIds as $orderId) {
            $order = SalesOrder::query()
                ->where('tenant_id', $tenantId)
                ->whereKey($orderId)
                ->with(['lines' => static fn ($q) => $q->orderBy('id')])
                ->first();
            if ($order === null) {
                $failures[] = ['id' => $orderId, 'message' => __('sales.bulk.not_found')];

                continue;
            }
            if (! Gate::forUser($user)->allows('allocate', $order)) {
                $failures[] = ['id' => $orderId, 'message' => __('sales.bulk.forbidden')];

                continue;
            }
            $rows = [];
            foreach ($order->lines as $line) {
                $open = SalesLineRemainingCalculator::allocatableRemaining($line);
                if (DecimalQuantity::gtZero($open)) {
                    $rows[] = ['line_id' => (int) $line->id, 'quantity' => $open];
                }
            }
            if ($rows === []) {
                $failures[] = ['id' => $orderId, 'message' => __('sales.bulk.nothing_to_allocate')];

                continue;
            }
            try {
                $this->batch->allocateLines(
                    $tenantId,
                    (int) $order->id,
                    $rows,
                    (string) Str::uuid(),
                    (int) $user->id,
                );
                $succeeded[] = (int) $order->id;
            } catch (SalesOperationalException|InsufficientStockException $e) {
                $failures[] = ['id' => $orderId, 'message' => SalesOperationalWebExceptionMapper::toOperationalMessage($e)];
            } catch (\Throwable $e) {
                $failures[] = ['id' => $orderId, 'message' => $e->getMessage()];
            }
        }

        return new BulkOperationResult($succeeded, $failures, __('sales.bulk.label_allocate'));
    }

    /**
     * @param  list<int|string>  $orderIds
     * @return list<int>
     */
    private function normalizeIds(array $orderIds): array
    {
        $out = [];
        foreach ($orderIds as $id) {
            $out[] = (int) $id;
        }
        $out = array_values(array_unique(array_filter($out, static fn (int $v) => $v > 0)));

        return array_slice($out, 0, 25);
    }
}
