<?php

use App\Actions\GrantSubscription;
use App\Models\Customer;
use App\Models\Operator;
use App\Models\Order;
use App\Models\ProvisioningRun;
use App\Models\Subscription;
use App\Provisioning\Jobs\AdvanceRunJob;
use App\Services\Billing\PlanCatalogue;
use Illuminate\Support\Facades\Queue;

/**
 * A grant is an order without money, not a second code path: it must produce
 * the same rows — order, provisioning run, subscription — a paid purchase
 * does, with provenance recorded and price_cents set to what the customer
 * actually pays.
 */
it('opens a subscription with no money charged, and provenance recorded', function () {
    Queue::fake();

    $customer = Customer::factory()->create();
    $owner = Operator::factory()->create();

    $subscription = app(GrantSubscription::class)(
        customer: $customer,
        grantedBy: $owner,
        plan: 'team',
        term: Subscription::TERM_MONTHLY,
        datacenter: 'fsn',
        priceCents: 0,
        note: 'Freund des Hauses',
    );

    $order = Order::query()->where('customer_id', $customer->id)->sole();

    expect($order->amount_cents)->toBe(0)
        ->and($order->stripe_event_id)->toBeNull()
        ->and($subscription->price_cents)->toBe(0)
        ->and($subscription->plan)->toBe('team')
        // The customer is owed the real machine, so the snapshot is the
        // catalogue's — only the price and the provenance were overridden.
        ->and($subscription->ram_mb)->toBeGreaterThan(0)
        ->and($subscription->isGranted())->toBeTrue()
        ->and($subscription->isFreeGrant())->toBeTrue()
        ->and($subscription->granted_by)->toBe($owner->id)
        ->and($subscription->grant_note)->toBe('Freund des Hauses')
        ->and($subscription->granted_until)->toBeNull()
        ->and($subscription->catalogue_price_cents)->toBeGreaterThan(0);

    // Same downstream pipeline as a paid order: a run was opened and dispatched.
    $run = ProvisioningRun::query()->where('subject_id', $order->id)->sole();
    expect($run->pipeline)->toBe('customer');
    Queue::assertPushed(AdvanceRunJob::class, fn ($job) => $job->runUuid === $run->uuid);
});

it('records a discount as what it is: a price below the catalogue, not a free plan', function () {
    $customer = Customer::factory()->create();
    $owner = Operator::factory()->create();

    $subscription = app(GrantSubscription::class)(
        customer: $customer,
        grantedBy: $owner,
        plan: 'team',
        term: Subscription::TERM_MONTHLY,
        datacenter: 'fsn',
        priceCents: 5000,
    );

    expect($subscription->price_cents)->toBe(5000)
        ->and($subscription->catalogue_price_cents)->toBeGreaterThan(5000)
        ->and($subscription->isFreeGrant())->toBeFalse()
        ->and($subscription->isGranted())->toBeTrue();
});

it('grants a quota bonus beyond the plan, baked into the frozen snapshot', function () {
    $customer = Customer::factory()->create();
    $owner = Operator::factory()->create();
    $plainTeam = app(PlanCatalogue::class)->currentVersion('team');

    $subscription = app(GrantSubscription::class)(
        customer: $customer,
        grantedBy: $owner,
        plan: 'team',
        term: Subscription::TERM_MONTHLY,
        datacenter: 'fsn',
        priceCents: 0,
        bonus: ['quota_gb' => $plainTeam->quota_gb + 250],
    );

    expect($subscription->quota_gb)->toBe($plainTeam->quota_gb + 250);
});

it('refuses to charge more for a grant than the plan actually costs', function () {
    $customer = Customer::factory()->create();
    $owner = Operator::factory()->create();
    $price = app(PlanCatalogue::class)->currentVersion('team')->priceFor('monthly')->amount_cents;

    expect(fn () => app(GrantSubscription::class)(
        customer: $customer,
        grantedBy: $owner,
        plan: 'team',
        term: Subscription::TERM_MONTHLY,
        datacenter: 'fsn',
        priceCents: $price + 1,
    ))->toThrow(RuntimeException::class);
});

it('remembers until when a dated grant runs, and leaves it unlimited otherwise', function () {
    $customer = Customer::factory()->create();
    $owner = Operator::factory()->create();
    $until = now()->addMonths(6);

    $subscription = app(GrantSubscription::class)(
        customer: $customer,
        grantedBy: $owner,
        plan: 'team',
        term: Subscription::TERM_MONTHLY,
        datacenter: 'fsn',
        priceCents: 0,
        until: $until,
    );

    expect($subscription->granted_until->toDateString())->toBe($until->toDateString());
});
