<?php

namespace App\Console\Commands;

use App\Models\StatusDay;
use App\Services\Status\ServiceHealth;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

/**
 * Writes one sample of every public component into today's row.
 *
 * The ninety-day bar on the status page is the one figure a reader checks
 * against their own memory, so it has to come from something that was actually
 * recorded at the time. There is no way to reconstruct it afterwards: the
 * monitoring table holds the LAST verdict per instance, not a history, and by
 * the time anybody asks about last Tuesday that verdict is about today.
 *
 * Runs on the scheduler beside the monitoring sync it depends on. A missed run
 * costs one sample out of roughly three hundred in a day; a missed DAY leaves
 * no row at all, and the page draws that as "not recorded" rather than
 * inventing a colour for it.
 */
class SampleServiceStatus extends Command
{
    protected $signature = 'clupilot:sample-status';

    protected $description = 'Record the current state of every public status component into the daily history';

    public function handle(ServiceHealth $health): int
    {
        // The wall clock, not UTC: the bar is labelled with days a reader
        // recognises, and a sample taken at 01:30 local belongs to the day they
        // would call it (R19).
        $day = Carbon::now()->local()->toDateString();

        foreach ($health->components() as $component) {
            $state = $component['state'];

            // One statement, so two schedulers racing on the same minute cannot
            // read-modify-write over each other. The counters are increments,
            // never assignments — a sample is a thing that happened, and a
            // later run must not be able to undo it.
            DB::table('status_days')->upsert(
                [[
                    'day' => $day,
                    'component' => $component['key'],
                    'samples' => 1,
                    'operational' => $state === 'operational' ? 1 : 0,
                    'degraded' => $state === 'degraded' ? 1 : 0,
                    'down' => $state === 'down' ? 1 : 0,
                    'unknown' => $state === 'unknown' ? 1 : 0,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]],
                ['day', 'component'],
                [
                    'samples' => DB::raw('samples + 1'),
                    'operational' => DB::raw('operational + '.($state === 'operational' ? 1 : 0)),
                    'degraded' => DB::raw('degraded + '.($state === 'degraded' ? 1 : 0)),
                    'down' => DB::raw('down + '.($state === 'down' ? 1 : 0)),
                    'unknown' => DB::raw('unknown + '.($state === 'unknown' ? 1 : 0)),
                    'updated_at' => DB::raw('CURRENT_TIMESTAMP'),
                ],
            );
        }

        // Beyond the window the page can draw, the rows are only weight. Kept a
        // fortnight longer than the ninety days shown so a change of window
        // does not immediately hit an empty table.
        StatusDay::query()
            ->where('day', '<', Carbon::now()->local()->subDays(104)->toDateString())
            ->delete();

        return self::SUCCESS;
    }
}
