<?php

namespace App\Modules\Sales\Services;

use App\Modules\Inventory\Reservation\DTOs\ReleaseInventoryInput;
use App\Modules\Inventory\Reservation\Services\ReleaseInventoryService;
use App\Modules\Sales\Exceptions\SalesOperationalException;
use App\Modules\Sales\Models\SalesOrder;
use App\Modules\Sales\Repositories\SalesOrderLineRepository;
use App\Modules\Sales\Repositories\SalesOrderRepository;
use App\Modules\Sales\Support\SalesIdempotencySources;
use App\Modules\Sales\Support\SalesOrderLifecycleStatus;
use App\Modules\Inventory\Stock\Support\DecimalQuantity;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

final class SalesOrderLifecycleService
{
    public function __construct(
        private readonly SalesOrderRepository $orders,
        private readonly SalesOrderLineRepository $lines,
        private readonly ReleaseInventoryService $release,
    ) {}

    public function confirmDraft(int $tenantId, int $salesOrderId): void
    {
        DB::transaction(function () use ($tenantId, $salesOrderId): void {
            $order = $this->requireOrder($tenantId, $salesOrderId);
            $from = $this->parseOrFail($order);
            if (! SalesOrderLifecycleRules::allowsConfirm($from)) {
                throw new SalesOperationalException('Invalid confirm transition.', 'sales_confirm_invalid');
            }
            $order->forceFill(['status' => SalesOrderLifecycleStatus::Confirmed->value])->save();
        });
    }

    public function cancelOrder(int $tenantId, int $salesOrderId, ?string $correlationId): void
    {
        $attempts = (int) config('inventory.transaction_attempts', 5);
        DB::transaction(function () use ($tenantId, $salesOrderId, $correlationId): void {
            $order = $this->requireOrder($tenantId, $salesOrderId);
            $from = $this->parseOrFail($order);
            if (! SalesOrderLifecycleRules::allowsCancel($from)) {
                throw new SalesOperationalException('Order cannot be cancelled.', 'sales_cancel_invalid');
            }
            $lockedLines = $this->lines->lockForOrderOrdered($tenantId, $salesOrderId);
            $this->releaseAllOpenReservations($tenantId, $order, $lockedLines, $correlationId);
            $order->forceFill(['status' => SalesOrderLifecycleStatus::Cancelled->value])->save();
        }, $attempts);
    }

    private function releaseAllOpenReservations(
        int $tenantId,
        SalesOrder $order,
        $lockedLines,
        ?string $correlationId,
    ): void {
        $cid = $correlationId ?? (string) Str::uuid();
        foreach ($lockedLines as $line) {
            $open = trim((string) $line->quantity_reserved);
            if (! DecimalQuantity::gtZero($open)) {
                continue;
            }
            $rid = (string) $line->reservation_public_id;
            if ($rid === '') {
                throw new SalesOperationalException('Line reservation missing.', 'sales_cancel_reservation_missing');
            }
            $key = 'cancel_o'.$order->id.'_l'.$line->id;
            $this->release->release(new ReleaseInventoryInput(
                tenantId: $tenantId,
                warehouseId: (int) $line->warehouse_id,
                itemId: (int) $line->item_id,
                reservationPublicId: $rid,
                quantity: $open,
                actorUserId: null,
                correlationId: $cid,
                idempotencyKey: $key,
                idempotencySource: SalesIdempotencySources::CancelRelease,
                sourceType: 'sales_order_line',
                sourceId: (int) $line->id,
                metadata: ['sales_order_id' => (int) $order->id],
            ));
            $line->forceFill(['quantity_reserved' => '0'])->save();
        }
    }

    private function requireOrder(int $tenantId, int $salesOrderId): SalesOrder
    {
        $order = $this->orders->lockByIdForTenant($tenantId, $salesOrderId);
        if ($order === null) {
            throw new SalesOperationalException('Sales order not found.', 'sales_order_not_found');
        }

        return $order;
    }

    private function parseOrFail(SalesOrder $order): SalesOrderLifecycleStatus
    {
        $s = SalesOrderLifecycleStatus::tryFrom((string) $order->status);
        if ($s === null) {
            throw new SalesOperationalException('Unknown sales order status.', 'sales_unknown_status');
        }

        return $s;
    }
}
