<?php

namespace App\Actions;

use App\Models\Operator;
use App\Models\Order;
use App\Models\Subscription;
use App\Models\SubscriptionAddon;
use App\Services\Billing\AddonCatalogue;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * Gives a customer a module without a payment — "ein Plugin schenken".
 *
 * Books through the same BookAddon a paid order uses, on a synthetic Order
 * (no Stripe id, price set to what the customer actually pays) so the module
 * ends up frozen on `subscription_addons` exactly like a real booking would.
 */
class GrantAddon
{
    public function __construct(private BookAddon $bookAddon) {}

    public function __invoke(
        Subscription $subscription,
        Operator $grantedBy,
        string $addonKey,
        int $priceCents,
        int $quantity = 1,
        ?string $note = null,
        ?Carbon $until = null,
    ): SubscriptionAddon {
        $cataloguePrice = app(AddonCatalogue::class)->priceCents($addonKey);

        if ($cataloguePrice === null) {
            throw new RuntimeException("Unknown add-on: {$addonKey}");
        }

        if ($priceCents < 0 || $priceCents > $cataloguePrice) {
            throw new RuntimeException('A grant cannot charge more than the catalogue price.');
        }

        // Vestigial for a module — nothing is provisioned at a datacenter for
        // it — but the column is not nullable, and the contract's own order
        // already carries the right one.
        $datacenter = $subscription->order?->datacenter ?? 'fsn';

        // The order and the booking together, or neither. Booking can refuse —
        // not every module is for sale on every package — and an order left
        // behind on its own is a paid purchase of nothing: it shows up in the
        // customer's orders and on their invoice, for a module they never got.
        return DB::transaction(function () use ($subscription, $grantedBy, $addonKey, $priceCents, $quantity, $note, $until, $cataloguePrice, $datacenter) {
            $order = Order::create([
                'customer_id' => $subscription->customer_id,
                'plan' => $subscription->plan,
                'type' => 'addon',
                'addon_key' => $addonKey,
                'amount_cents' => $priceCents,
                'currency' => Subscription::catalogueCurrency(),
                'datacenter' => $datacenter,
                'stripe_event_id' => null,
                'stripe_subscription_id' => null,
                'status' => 'paid',
            ]);

            return ($this->bookAddon)($subscription, $addonKey, $quantity, $order, [
                'price_cents' => $priceCents,
                'granted_by' => $grantedBy->id,
                'granted_at' => now(),
                'grant_note' => $note,
                'granted_until' => $until,
                'catalogue_price_cents' => $cataloguePrice,
            ]);
        });
    }
}
