<?php

namespace App\Services\Billing;

use App\Models\Subscription;

/**
 * What the modules cost today, and what THIS customer pays for the ones they
 * have already booked.
 *
 * Two different questions, and the whole point of Phase 4 is that they have
 * different answers. A booked module is a price the customer agreed to; an
 * unbooked one is a sale we have not made yet, at whatever it costs now.
 *
 * Storage and the recurring modules live in config/provisioning.php. Unlike the
 * plans, they were never versioned or scheduled, so moving them into tables
 * would buy nothing — the freezing that matters happens on
 * `subscription_addons`, which is where a customer's own price lives.
 */
final class AddonCatalogue
{
    /** The extra-storage pack is priced separately from the module list. */
    public const STORAGE = 'storage';

    /** Today's monthly net price for one unit, or null if we do not sell it. */
    public function priceCents(string $key): ?int
    {
        if ($key === self::STORAGE) {
            return (int) config('provisioning.storage_addon.price_cents', 0);
        }

        $addon = (array) config("provisioning.addons.{$key}");

        return $addon === [] ? null : (int) ($addon['price_cents'] ?? 0);
    }

    public function knows(string $key): bool
    {
        return $this->priceCents($key) !== null;
    }

    /**
     * Every module we sell, each answered for this customer: booked ones at
     * the price they were booked at, the rest at today's.
     *
     * A module can be booked more than once — storage is sold in packs, and two
     * orders can each carry one — and each booking keeps its own price. So the
     * bookings are summed rather than reduced to one row: showing a single
     * arbitrary price while charging for all of them would let the page and the
     * bill say different things.
     *
     * @return array<string, array{key: string, price_cents: ?int, monthly_cents: int, booked: bool, quantity: int, bookings: array<int, array{price_cents: int, quantity: int}>}>
     */
    public function forSubscription(?Subscription $subscription): array
    {
        $booked = $subscription?->addons()->active()->orderBy('id')->get()->groupBy('addon_key')
            ?? collect();

        $keys = array_merge(array_keys((array) config('provisioning.addons')), [self::STORAGE]);

        $rows = [];

        foreach ($keys as $key) {
            $own = $booked->get($key) ?? collect();
            $prices = $own->pluck('price_cents')->unique();

            $rows[$key] = [
                'key' => $key,
                // Their price if they have exactly one, today's if they have
                // none — and null when their bookings disagree, because there
                // is no single honest figure to print.
                'price_cents' => match (true) {
                    $own->isEmpty() => $this->priceCents($key),
                    $prices->count() === 1 => (int) $prices->first(),
                    default => null,
                },
                'monthly_cents' => (int) $own->sum(fn ($addon) => $addon->monthlyCents()),
                'booked' => $own->isNotEmpty(),
                // A granted module shows without a price too, the same rule as
                // a granted plan — "ein Plugin schenken" should not read as
                // "kostenlos" on the very page that sells it to everyone else.
                'granted' => $own->isNotEmpty() && $own->every(fn ($addon) => $addon->isGranted()),
                'quantity' => (int) $own->sum('quantity'),
                'bookings' => $own->map(fn ($addon) => [
                    'price_cents' => $addon->price_cents,
                    'quantity' => $addon->quantity,
                ])->values()->all(),
            ];
        }

        return $rows;
    }
}
