<?php

namespace App\Services\Orders;

use App\Events\OrderStatusUpdated;
use App\Models\Order;
use App\Services\Invoicing\InvoiceService;
use App\Services\Inventory\StockService;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * The single place an order's status is allowed to move - originally lived
 * directly in Api\Franchise\OrderFulfillmentController, pulled out once
 * Delivery Agent actions (picked-up -> out_for_delivery, delivered ->
 * delivered) needed to drive the exact same transitions, FEFO deduction,
 * and invoice generation rather than a second copy of that logic.
 */
class OrderFulfillmentService
{
    /** Which statuses a given transition is allowed to start from. */
    private const array ALLOWED_FROM = [
        'preparing' => ['confirmed'],
        'ready_for_dispatch' => ['preparing'],
        'out_for_delivery' => ['ready_for_dispatch'],
        'delivered' => ['out_for_delivery', 'ready_for_dispatch'],
        'picked_up' => ['ready_for_dispatch'],
    ];

    public function __construct(
        private readonly StockService $stock,
        private readonly InvoiceService $invoices,
    ) {}

    /**
     * @throws RuntimeException if the transition isn't valid from the order's current status
     * @throws \App\Exceptions\InsufficientStockException if stock moved between reservation and fulfillment
     */
    public function transition(Order $order, string $newStatus): Order
    {
        $allowedFrom = self::ALLOWED_FROM[$newStatus] ?? [];

        if (! in_array($order->status->value, $allowedFrom, true)) {
            throw new RuntimeException("Can't move from \"{$order->status->value}\" to \"{$newStatus}\".");
        }

        DB::transaction(function () use ($order, $newStatus) {
            $isFulfilling = in_array($newStatus, ['delivered', 'picked_up'], true);

            if ($isFulfilling) {
                // The real deduction: FEFO batch selection + order_item_batches trail.
                $this->stock->fulfillOrder($order);
            }

            $timestamps = match ($newStatus) {
                'preparing' => ['prepared_at' => now()],
                'out_for_delivery' => ['out_for_delivery_at' => now()],
                // Reused for both terminal states rather than a separate
                // picked_up_at column for what's functionally the same
                // "fulfillment completed" moment.
                'delivered', 'picked_up' => ['delivered_at' => now()],
                default => [],
            };

            $order->update([...$timestamps, 'status' => $newStatus]);

            if ($isFulfilling) {
                // Revenue recognised + GST invoice, matching the SRS's own
                // ordering: fulfillment is what turns payment into revenue.
                $this->invoices->generateFor($order->fresh());
            }
        });

        $order = $order->fresh(['items.product', 'items.batches']);

        OrderStatusUpdated::dispatch($order);

        return $order;
    }
}
