<?php

namespace App\Services\Billing;

use App\Support\Settings;
use Illuminate\Support\Carbon;

/**
 * What the last `stripe:sync-catalogue` run printed, and when.
 *
 * A settings row rather than a table, for the same reason InboundMailStatus is
 * one: there is exactly one catalogue and exactly one last answer, and a table
 * of one row is a table somebody has to prune.
 *
 * The OUTPUT is kept whole and unedited. Two of the things this command says
 * are instructions rather than statuses — the refusal to touch a catalogue that
 * belongs to the other Stripe account names the three places an operator has to
 * clear, and the duplicate-product warning names an id only a person can
 * resolve. Summarising either into "sync failed" would take away exactly the
 * sentence that was worth reading, and send the operator back to the terminal
 * this page exists to replace.
 */
class CatalogueSyncStatus
{
    private const KEY = 'billing.catalogue_sync';

    /**
     * How much of the run's output is kept.
     *
     * Generous on purpose: a first run on a full catalogue prints a line per
     * product and per price pair, and the interesting part of a long run is the
     * end. Bounded all the same, because this is a settings row that gets read
     * on every render of the page.
     */
    private const MAX_OUTPUT = 20000;

    public function record(bool $ok, bool $dryRun, string $output, string $by): void
    {
        Settings::set(self::KEY, [
            'at' => Carbon::now()->toIso8601String(),
            'ok' => $ok,
            'dry_run' => $dryRun,
            // Tail, not head: the summary line an operator is looking for
            // ("N object(s) would be created", or the refusal) is the last
            // thing printed, and a head-truncated log would cut off exactly it.
            'output' => mb_strlen($output) > self::MAX_OUTPUT
                ? '…'.mb_substr($output, -self::MAX_OUTPUT)
                : $output,
            'by' => $by,
        ]);
    }

    /**
     * The last run, or null if nobody has ever asked.
     *
     * @return array{at: Carbon, ok: bool, dry_run: bool, output: string, by: string}|null
     */
    public function last(): ?array
    {
        $row = Settings::get(self::KEY);

        if (! is_array($row) || ! isset($row['at'])) {
            return null;
        }

        return [
            // Parsed back into a Carbon so the view can put it on the wall
            // clock (R19) rather than printing an ISO string at somebody.
            'at' => Carbon::parse((string) $row['at']),
            'ok' => (bool) ($row['ok'] ?? false),
            'dry_run' => (bool) ($row['dry_run'] ?? false),
            'output' => (string) ($row['output'] ?? ''),
            'by' => (string) ($row['by'] ?? ''),
        ];
    }
}
