<?php

namespace App\Console\Commands;

use App\Models\PlanFamily;
use App\Models\PlanVersion;
use App\Models\Subscription;
use Illuminate\Console\Command;

/**
 * Tells the owner whether the catalogue is in a state that can actually sell.
 *
 * Everything here is computed at read time, which means a broken window or a
 * missing price does not announce itself until a customer hits it. This is the
 * thing to run after editing plans — before finding out from a failed checkout.
 */
class CheckPlanCatalogue extends Command
{
    protected $signature = 'plans:check';

    protected $description = 'Check the plan catalogue for overlaps, gaps and missing prices';

    public function handle(): int
    {
        $currency = Subscription::catalogueCurrency();
        $problems = [];
        $now = now();

        $families = PlanFamily::query()->with('versions.prices')->orderBy('tier')->get();

        if ($families->isEmpty()) {
            $this->error('The catalogue is empty. Nothing can be sold.');

            return self::FAILURE;
        }

        foreach ($families as $family) {
            $live = $family->versions->filter(fn (PlanVersion $v) => $v->isAvailableAt($now));

            if ($live->count() > 1) {
                $problems[] = "{$family->key}: {$live->count()} versions on sale at once (".
                    $live->pluck('version')->implode(', ').') — overlapping windows.';
            }

            if ($family->sales_enabled && $live->isEmpty()) {
                $problems[] = "{$family->key}: on sale, but no version is available right now.";
            }

            foreach ($family->versions as $version) {
                if (! $version->isPublished()) {
                    continue;
                }

                foreach ([Subscription::TERM_MONTHLY, Subscription::TERM_YEARLY] as $term) {
                    $priced = $version->prices
                        ->first(fn ($p) => $p->term === $term && $p->currency === $currency);

                    if ($priced === null) {
                        $problems[] = "{$family->key} v{$version->version}: no {$term} price in {$currency}.";
                    }
                }
            }
        }

        // A contract that cannot say which version it was sold under has lost
        // its provenance, which is the one thing the version table is for.
        $orphaned = Subscription::query()->whereNull('plan_version_id')->count();

        if ($orphaned > 0) {
            $problems[] = "{$orphaned} subscription(s) have no plan version recorded.";
        }

        foreach ($families as $family) {
            $state = $family->sales_enabled ? 'on sale' : 'withdrawn';
            $version = $family->versions->first(fn (PlanVersion $v) => $v->isAvailableAt($now));
            $this->line(sprintf(
                '  %-12s tier %d  %-10s %s',
                $family->key,
                $family->tier,
                $state,
                $version !== null ? "v{$version->version}" : '—',
            ));
        }

        if ($problems === []) {
            $this->newLine();
            $this->info('Catalogue is consistent.');

            return self::SUCCESS;
        }

        $this->newLine();
        foreach ($problems as $problem) {
            $this->error('  '.$problem);
        }

        return self::FAILURE;
    }
}
