<?php

use App\Livewire\Billing;
use App\Models\Customer;
use App\Models\Instance;
use App\Models\InstanceMetric;
use App\Models\Order;
use App\Models\Seat;
use App\Models\User;
use App\Services\Billing\DowngradeCheck;
use Livewire\Livewire;

/**
 * Moving down to a smaller plan.
 *
 * A downgrade is not the mirror of an upgrade: going up always fits, going
 * down can ask the instance to hold more than the target allows. What matters
 * as much as refusing it is SAYING WHY, in the customer's own numbers — a
 * disabled button that does not name the obstacle is the thing people ring
 * about.
 */
function downgradeCustomer(int $seats = 1, ?int $usedBytes = null): array
{
    $user = User::factory()->create();
    $customer = Customer::factory()->create(['user_id' => $user->id]);
    $instance = Instance::factory()->create(['customer_id' => $customer->id, 'status' => 'active', 'quota_gb' => 500]);

    foreach (range(1, $seats) as $n) {
        Seat::create([
            'customer_id' => $customer->id,
            'email' => "seat{$n}@example.test",
            'role' => $n === 1 ? 'owner' : 'member',
            'status' => 'active',
        ]);
    }

    if ($usedBytes !== null) {
        InstanceMetric::create([
            'instance_id' => $instance->id,
            'day' => now()->toDateString(),
            'disk_used_bytes' => $usedBytes,
            'disk_total_bytes' => 500 * 1024 ** 3,
        ]);
    }

    // One test below calls Billing::purchase(), which since Task 5 refuses
    // outright without a Stripe key for the active mode. Without this, that
    // test would see the same "no order" result for the wrong reason — the
    // Stripe guard, not the seat block it names — and prove nothing.
    withStripeSecret();

    return [$user, $customer, $instance];
}

it('allows a downgrade when everything fits', function () {
    [, $customer, $instance] = downgradeCustomer(seats: 3, usedBytes: 40 * 1024 ** 3);

    $check = DowngradeCheck::for($customer, $instance, ['seats' => 10, 'quota_gb' => 200]);

    expect($check->allowed)->toBeTrue()->and($check->blockers)->toBe([]);
});

it('names the seats that are in the way, with both numbers', function () {
    [, $customer, $instance] = downgradeCustomer(seats: 31);

    $check = DowngradeCheck::for($customer, $instance, ['seats' => 10, 'quota_gb' => 200]);

    expect($check->allowed)->toBeFalse()
        ->and($check->blockers[0]['key'])->toBe('seats')
        ->and($check->blockers[0]['current'])->toBe('31')
        ->and($check->blockers[0]['limit'])->toBe('10');
});

it('measures storage rather than trusting the allowance', function () {
    // 480 GB really in use. The contract says 500 GB, but what decides whether
    // 200 GB is enough is what is actually stored.
    [, $customer, $instance] = downgradeCustomer(seats: 2, usedBytes: 480 * 1024 ** 3);

    $check = DowngradeCheck::for($customer, $instance, ['seats' => 10, 'quota_gb' => 200]);

    expect($check->allowed)->toBeFalse()
        ->and(collect($check->blockers)->pluck('key')->all())->toBe(['storage']);
});

it('does not refuse a customer who has simply never been measured', function () {
    // A new instance has no reading. Refusing on the contractual allowance
    // would block every downgrade for anyone who once bought a large plan.
    [, $customer, $instance] = downgradeCustomer(seats: 2, usedBytes: null);

    expect(DowngradeCheck::for($customer, $instance, ['seats' => 10, 'quota_gb' => 200])->allowed)->toBeTrue();
});

it('refuses a blocked downgrade even when the button is never rendered', function () {
    // The action is callable from a stale tab or a second window. A limit
    // enforced only in markup is not enforced.
    [$user, $customer, $instance] = downgradeCustomer(seats: 31);

    Livewire::actingAs($user)
        ->test(Billing::class)
        ->call('purchase', 'downgrade', 'start');

    expect(Order::query()->where('customer_id', $customer->id)->where('type', 'downgrade')->count())->toBe(0);
});
