<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

/**
 * A day in the life of one instance: how full it was, and what it moved.
 *
 * Read by the customer panel to draw the storage ring and the transfer trend.
 * Written by CollectInstanceTraffic, which already visits every active
 * instance — a second scheduler entry hitting the same Proxmox API would only
 * double the load and the failure modes.
 */
class InstanceMetric extends Model
{
    protected $fillable = ['instance_id', 'day', 'disk_used_bytes', 'disk_total_bytes', 'rx_bytes', 'tx_bytes', 'checks_total', 'checks_ok'];

    protected function casts(): array
    {
        return [
            'day' => 'date',
            'disk_used_bytes' => 'integer',
            'disk_total_bytes' => 'integer',
            'rx_bytes' => 'integer',
            'tx_bytes' => 'integer',
            'checks_total' => 'integer',
            'checks_ok' => 'integer',
        ];
    }

    public function instance(): BelongsTo
    {
        return $this->belongsTo(Instance::class);
    }

    /**
     * The last $days days, oldest first, with gaps left as gaps.
     *
     * A missing day is not a zero. Filling it would draw a cliff into the chart
     * on every day the sampler could not reach the host, and the customer would
     * read an outage that never happened.
     *
     * @return Collection<int, self>
     */
    public static function series(Instance $instance, int $days = 14): Collection
    {
        return self::query()
            ->where('instance_id', $instance->id)
            ->where('day', '>=', Carbon::today()->subDays($days - 1))
            ->orderBy('day')
            ->get();
    }

    /**
     * Availability over the window, as a percentage, or null.
     *
     * Null when nothing was ever checked. Reporting an unmonitored instance as
     * 100 % is the single most dishonest number this application could print —
     * it is the figure a customer would quote to their own auditor.
     */
    public static function availability(Instance $instance, int $days = 30): ?float
    {
        $row = self::query()
            ->where('instance_id', $instance->id)
            ->where('day', '>=', Carbon::today()->subDays($days - 1))
            ->selectRaw('SUM(checks_total) as total, SUM(checks_ok) as ok')
            ->first();

        $total = (int) ($row->total ?? 0);

        return $total === 0 ? null : round((int) $row->ok / $total * 100, 2);
    }

    /**
     * Daily availability for the trend, oldest first.
     *
     * Days without a single check are skipped rather than drawn at zero: our
     * monitoring being down is not the customer's cloud being down.
     *
     * @return array<int, float>
     */
    public static function availabilitySeries(Instance $instance, int $days = 30): array
    {
        return self::query()
            ->where('instance_id', $instance->id)
            ->where('day', '>=', Carbon::today()->subDays($days - 1))
            ->where('checks_total', '>', 0)
            ->orderBy('day')
            ->get()
            ->map(fn (self $m) => round($m->checks_ok / max(1, $m->checks_total) * 100, 2))
            ->all();
    }

    /** The most recent day that carries a disk reading, or null. */
    public static function latestDisk(Instance $instance): ?self
    {
        return self::query()
            ->where('instance_id', $instance->id)
            ->whereNotNull('disk_used_bytes')
            ->orderByDesc('day')
            ->first();
    }
}
