<?php

namespace App\Console\Commands;

use App\Models\LocationCheckResult;
use App\Models\Server;
use App\Models\UptimeBucket;
use App\Models\Website;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Roll up location_check_results into uptime_buckets.
 *
 * Runs hourly by the scheduler. Aggregates the previous full hour's check
 * results into:
 *   - one bucket per (target, location) — per-location uptime
 *   - one bucket per target with location_id=null — consensus uptime
 *
 * After successful aggregation, raw check results older than 7 days are
 * pruned (separately, by PruneOldCheckResultsCommand).
 *
 * The buckets table is the source of truth for "uptime over the last
 * 30/90/365 days" queries — the raw table doesn't scale to those windows.
 */
class AggregateUptimeBucketsCommand extends Command
{
    protected $signature = 'noc:aggregate-uptime-buckets
                            {--hours=2 : How many hours back to (re)aggregate}';

    protected $description = 'Roll location_check_results into hourly uptime_buckets';

    public function handle(): int
    {
        $hours = (int) $this->option('hours');
        // Aggregate the last N hours so a delayed cron run doesn't lose data.
        // The unique index on uptime_buckets prevents duplicates when we
        // re-run for already-processed hours.
        $end = now()->startOfHour();
        $start = $end->copy()->subHours($hours);

        $this->info("Aggregating buckets from {$start} to {$end}");

        $totalBuckets = 0;
        for ($hour = $start->copy(); $hour->lt($end); $hour->addHour()) {
            $totalBuckets += $this->aggregateHour($hour);
        }

        $this->info("Wrote {$totalBuckets} bucket rows");
        return self::SUCCESS;
    }

    /**
     * Aggregate one specific hour's worth of check results into buckets.
     */
    protected function aggregateHour(Carbon $hourStart): int
    {
        $hourEnd = $hourStart->copy()->addHour();
        $count = 0;

        // Per-location buckets: one row per (target, location, hour).
        // The index on (target_type, target_id, checked_at) makes this fast.
        $perLocation = DB::table('location_check_results')
            ->whereBetween('checked_at', [$hourStart, $hourEnd])
            ->selectRaw('
                target_type,
                target_id,
                probe_location_id,
                COUNT(*) as total,
                SUM(is_up) as up,
                AVG(response_time_ms) as avg_ms
            ')
            ->groupBy('target_type', 'target_id', 'probe_location_id')
            ->get();

        foreach ($perLocation as $row) {
            UptimeBucket::updateOrCreate(
                [
                    'target_type' => $row->target_type,
                    'target_id' => $row->target_id,
                    'probe_location_id' => $row->probe_location_id,
                    'bucket_starts_at' => $hourStart,
                    'bucket_size' => 'hour',
                ],
                [
                    'checks_total' => (int) $row->total,
                    'checks_up' => (int) $row->up,
                    'uptime_pct' => $row->total > 0 ? round(($row->up / $row->total) * 100, 2) : 100.00,
                    'avg_response_ms' => $row->avg_ms ? (int) $row->avg_ms : null,
                ]
            );
            $count++;
        }

        // Consensus buckets (location_id = null): one row per (target, hour).
        // Logic: a target is "up for this hour" in proportion to how many
        // location-results said up. We use the same total/up counts but
        // aggregated across locations — same as treating each location
        // result as one independent vote.
        $consensus = DB::table('location_check_results')
            ->whereBetween('checked_at', [$hourStart, $hourEnd])
            ->selectRaw('
                target_type,
                target_id,
                COUNT(*) as total,
                SUM(is_up) as up,
                AVG(response_time_ms) as avg_ms
            ')
            ->groupBy('target_type', 'target_id')
            ->get();

        foreach ($consensus as $row) {
            UptimeBucket::updateOrCreate(
                [
                    'target_type' => $row->target_type,
                    'target_id' => $row->target_id,
                    'probe_location_id' => null,
                    'bucket_starts_at' => $hourStart,
                    'bucket_size' => 'hour',
                ],
                [
                    'checks_total' => (int) $row->total,
                    'checks_up' => (int) $row->up,
                    'uptime_pct' => $row->total > 0 ? round(($row->up / $row->total) * 100, 2) : 100.00,
                    'avg_response_ms' => $row->avg_ms ? (int) $row->avg_ms : null,
                ]
            );
            $count++;
        }

        return $count;
    }
}
