<?php

namespace App\Livewire;

use App\Models\Customer;
use App\Models\Subscription;
use App\Services\Billing\PlanCatalogue;
use App\Services\Billing\SetupFee;
use App\Services\Billing\TaxTreatment;
use App\Services\Provisioning\HostCapacity;
use App\Support\CompanyProfile;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Layout;
use Livewire\Component;
use Throwable;

/**
 * Choose a package and pay for it, without anybody writing an email.
 *
 * The website used to end in "anfragen" — a mailto: link and a promise to reply
 * the same working day. That is an afternoon of somebody's time per customer,
 * for a product whose whole point is that the machine does the work.
 *
 * The prices here are the catalogue's at the till, exactly as the public sheet
 * shows them: a customer who was quoted 58,80 € must not meet 49 € on the page
 * where they press the button, nor the other way round. This page differs from the
 * public sheet in one respect, and it is allowed to because it knows who is
 * reading it: a business in another member state with a verified VAT id is quoted
 * — and charged — the bare net, because reverse charge means no VAT is owed to us
 * at all. The delivery promise is the estate's own answer (HostCapacity), so
 * nobody is told "sofort" while a server still has to be bought.
 *
 * Nothing is charged here. The button opens a Stripe hosted checkout; the
 * purchase becomes real on the webhook, which is the only place allowed to
 * believe a payment happened.
 */
// layouts.portal is the bare shell the SIGN-IN pages use: no navigation, no
// page padding. This page is reached signed in and behind `verified`, so it
// belongs in the app shell like every other portal page — Dashboard and Billing
// were already there. In the wrong one it rendered flush against the top and
// bottom of the window with no way back to anything.
#[Layout('layouts.portal-app')]
class Order extends Component
{
    public function render()
    {
        $user = Auth::user();
        $customer = $user === null ? null : Customer::query()->where('email', $user->email)->first();

        // Somebody already on a contract is not buying a second cloud from
        // this page — changing package is a plan change, which prorates and
        // keeps their data where it is.
        $contract = $customer === null ? null : Subscription::query()
            ->where('customer_id', $customer->id)
            ->whereIn('status', ['active', 'past_due'])
            ->first();

        // Resolved once and used for every figure on the page, so the package
        // price, the setup fee and the sentence about VAT underneath them cannot
        // state three different treatments of one sale. It is the same call the
        // checkout makes to pick the Stripe Price, so what is shown here is what
        // will be taken.
        $tax = TaxTreatment::for($customer);

        return view('livewire.order', [
            'plans' => $this->plans($tax),
            'tax' => $tax,
            'contract' => $contract,
            'vat' => rtrim(rtrim(number_format(CompanyProfile::taxRate(), 1, ',', '.'), '0'), ','),
            // At the till, like the package price above it and like the public
            // sheet, which quotes "Einrichtung kostet einmalig 118,80 €". This page
            // was quoting the NET figure under the same sentence, so the site said
            // 118,80 and the page with the button on it said 99,00. Through the
            // same treatment as the package, because a fee is a supply like any
            // other and cannot be taxed differently from the thing it sets up.
            'setup' => SetupFee::chargedCents($tax),
        ]);
    }

    /**
     * The packages on sale, in the words and figures the public sheet uses —
     * priced for whoever is reading them.
     *
     * `gross` is what this customer's card would be charged, formed by
     * TaxTreatment and not by a multiplication of its own. It used to be the
     * latter, which is a second source for a number the checkout also forms, and
     * therefore a second answer waiting to happen.
     *
     * @return array<int, array<string, mixed>>
     */
    private function plans(TaxTreatment $tax): array
    {
        try {
            $sellable = app(PlanCatalogue::class)->sellable();
        } catch (Throwable) {
            // The catalogue fails loudly by design — an overlap must not decide
            // a customer's terms by row order. A portal page is not the place
            // to argue about it: no list, one sentence, and the operator's
            // console is already shouting.
            return [];
        }

        $capacity = app(HostCapacity::class);
        $plans = [];

        foreach ($sellable as $key => $plan) {
            $net = (int) $plan['price_cents'];
            $yearlyNet = (int) ($plan['yearly_price_cents'] ?? $net * 12);
            $yearlyGross = $tax->chargeCents($yearlyNet);
            $freeMonths = (int) ($plan['free_months'] ?? 0);

            $plans[] = [
                'key' => $key,
                'name' => (string) $plan['name'],
                'audience' => (string) ($plan['audience'] ?? ''),
                'gross' => $tax->chargeCents($net),
                'net' => $net,
                // Paying for a year, in the three figures somebody compares:
                // what leaves the account once, what that works out to per
                // month, and what it would have cost month by month.
                'yearly_gross' => $yearlyGross,
                'yearly_net' => $yearlyNet,
                // intdiv, so the headline figure never claims a tenth of a cent
                // nobody is charged. The total above it is the amount taken.
                'yearly_per_month' => intdiv($yearlyGross, 12),
                'twelve_months_gross' => $tax->chargeCents($net) * 12,
                'free_months' => $freeMonths,
                'currency' => (string) $plan['currency'],
                'quota_gb' => (int) $plan['quota_gb'],
                'traffic_gb' => (int) $plan['traffic_gb'],
                'seats' => (int) $plan['seats'],
                'recommended' => (bool) ($plan['recommended'] ?? false),
                'delivery' => $capacity->deliveryFor((int) ($plan['disk_gb'] ?? 0)),
            ];
        }

        return $plans;
    }
}
