<?php

use App\Livewire\Order as OrderPage;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\SubscriptionRecord;
use App\Models\User;
use App\Services\Stripe\FakeStripeClient;
use App\Services\Stripe\StripeClient;
use App\Support\CompanyProfile;
use App\Support\Settings;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

/**
 * The setup fee is charged, because it is advertised.
 *
 * It sat on the public price sheet ("Einrichtung kostet einmalig 118,80 €") and on
 * the booking page ("zzgl. Einrichtung einmalig") for months, the operator set the
 * figure in the console, and nothing anywhere ever charged it: the checkout posted
 * one recurring line item and there was no invoice item, no one-off line and no
 * reader of the figure outside the two sentences that quoted it.
 *
 * It rides along as a second, non-recurring line on the same Stripe session, which
 * Stripe puts on the initial invoice only — charged once, with the first month, and
 * never again.
 */
beforeEach(function () {
    $this->stripe = new FakeStripeClient;
    app()->instance(StripeClient::class, $this->stripe);

    DB::table('plan_prices')->update(['stripe_price_id' => 'price_test']);

    CompanyProfile::put([
        'name' => 'CluPilot Cloud e.U.', 'address' => 'Dreherstraße 66/1/8',
        'postcode' => '1110', 'city' => 'Wien', 'vat_id' => 'ATU00000000',
    ]);

    // 99 € net, which is the 118,80 € the price sheet quotes at 20 %.
    Settings::set('company.setup_fee_cents', 9900);
});

function setupFeeBuyer(): User
{
    return User::factory()->create(['email_verified_at' => now()]);
}

it('charges the fee it advertises, as a one-off line beside the monthly one', function () {
    $this->actingAs(setupFeeBuyer())
        ->post(route('checkout.start'), ['plan' => 'start', 'terms_accepted' => '1'])
        ->assertRedirect();

    $checkout = $this->stripe->checkouts[0];

    // Gross, like every other figure a domestic customer is charged: the price on
    // the page is the price at the till. A verified business in another member
    // state is charged the bare net of the fee, exactly as of the package — see
    // ReverseChargePriceTest.
    expect($checkout['one_off'])->not->toBeNull()
        ->and($checkout['one_off']['amount_cents'])->toBe(11880)
        ->and($checkout['one_off']['currency'])->toBe('EUR')
        ->and($checkout['one_off']['label'])->toBe(__('checkout.setup_fee_line'))
        // And the figure travels in the metadata, because the webhook has to be
        // able to tell the fee back out of Stripe's amount_total.
        ->and($checkout['metadata']['setup_fee_cents'])->toBe('11880');
});

it('sends no line at all when the operator charges no setup fee', function () {
    // Zero is not "a line for nought" on a checkout — it is a charge that does not
    // exist, and itemising it would tell the customer they owe nothing for
    // something.
    Settings::set('company.setup_fee_cents', 0);

    $this->actingAs(setupFeeBuyer())->post(route('checkout.start'), ['plan' => 'start', 'terms_accepted' => '1']);

    expect($this->stripe->checkouts[0]['one_off'])->toBeNull()
        // Stated as zero rather than left out. The webhook reads it either way,
        // and an explicit nought says this session was priced with no fee — as
        // opposed to having been opened by something that knew nothing about fees.
        ->and($this->stripe->checkouts[0]['metadata']['setup_fee_cents'])->toBe('0');
});

it('quotes the same figure on the booking page as the website and the till', function () {
    // The page was printing the NET figure under "zzgl. Einrichtung einmalig",
    // so the website said 118,80 € and the page with the button on it said 99 €.
    Livewire::actingAs(setupFeeBuyer())->test(OrderPage::class)
        ->assertViewHas('setup', 11880)
        ->assertSee('118,80');
});

it('puts the fee on the first invoice as its own line, and nowhere else', function () {
    Queue::fake();

    $this->postJson(route('webhooks.stripe'), [
        'id' => 'evt_setup',
        'type' => 'checkout.session.completed',
        'data' => ['object' => [
            'id' => 'cs_setup',
            'payment_status' => 'paid',
            'customer_details' => ['email' => 'einrichtung@example.com', 'name' => 'Kanzlei Berger'],
            // What Stripe took: 79,00 + 20 % for the package, plus 99,00 + 20 %
            // once for the setting-up.
            'amount_total' => 9480 + 11880,
            'currency' => 'eur',
            'metadata' => ['plan' => 'team', 'datacenter' => 'fsn', 'setup_fee_cents' => '11880'],
        ]],
    ])->assertOk();

    $order = Order::query()->where('stripe_event_id', 'cs_setup')->sole();

    // The whole of what was taken stays on the order — that is what a withdrawal
    // sends back — with the fee recorded beside it rather than subtracted from it.
    expect($order->amount_cents)->toBe(21360)
        ->and($order->setup_fee_cents)->toBe(11880)
        ->and($order->packageChargedCents())->toBe(9480);

    $invoice = Invoice::query()->where('order_id', $order->id)->sole();
    $lines = $invoice->snapshot['lines'];

    // Two lines, adding up to exactly what the card was charged. A fee folded into
    // the package's line would be a charge the customer can see on their bank
    // statement and cannot find on their invoice.
    expect($lines)->toHaveCount(2)
        ->and($lines[1]['description'])->toBe(__('checkout.setup_fee_line'))
        ->and($invoice->gross_cents)->toBe(21360);

    // And the register holds the PACKAGE's charge against the contract's price, so
    // a sale with a setup fee on it is not reported as charged at the wrong amount.
    $record = SubscriptionRecord::query()->where('event', SubscriptionRecord::EVENT_PURCHASE)->sole();

    expect($record->snapshot['amounts']['charged_gross_cents'])->toBe(9480)
        ->and($record->snapshot['amounts']['matches_catalogue'])->toBeTrue();
});

it('never lets a session claim more setup fee than it charged in total', function () {
    Queue::fake();

    $this->postJson(route('webhooks.stripe'), [
        'id' => 'evt_setup_wild',
        'type' => 'checkout.session.completed',
        'data' => ['object' => [
            'id' => 'cs_setup_wild',
            'payment_status' => 'paid',
            'customer_details' => ['email' => 'wild@example.com'],
            'amount_total' => 21480,
            'currency' => 'eur',
            // A figure no session of ours would carry. Unclamped it would put a
            // negative price on the contract's own invoice line.
            'metadata' => ['plan' => 'team', 'datacenter' => 'fsn', 'setup_fee_cents' => '99999'],
        ]],
    ])->assertOk();

    $order = Order::query()->where('stripe_event_id', 'cs_setup_wild')->sole();

    expect($order->setup_fee_cents)->toBe(21480)
        ->and($order->packageChargedCents())->toBe(0);
});
