<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;

/**
 * A threshold rule over a server metric. When a targeted server crosses it, the AlertEvaluator
 * opens a firing AlertIncident and notifies; when it recovers, the incident resolves.
 */
class AlertRule extends Model
{
    protected $guarded = [];

    protected $casts = [
        'enabled' => 'boolean',
        'threshold' => 'float', // % metrics are whole, but `load` is fractional (see the migration)
    ];

    /** Metrics a rule can watch. `offline` fires when a server can't be polled (status offline). */
    public const METRICS = ['cpu', 'mem', 'disk', 'load', 'offline'];

    public const COMPARATORS = ['gt', 'lt'];

    public const SCOPES = ['all', 'group', 'server'];

    protected static function booted(): void
    {
        static::creating(fn (self $r) => $r->uuid ??= (string) Str::uuid());
    }

    public function getRouteKeyName(): string
    {
        return 'uuid';
    }

    public function incidents(): HasMany
    {
        return $this->hasMany(AlertIncident::class);
    }

    /** Whether this rule applies to the given server (all / a group's members / one server). */
    public function targets(Server $server): bool
    {
        return match ($this->scope_type) {
            'server' => (int) $this->scope_id === $server->id,
            'group' => $server->groups()->where('server_groups.id', $this->scope_id)->exists(),
            default => true, // 'all'
        };
    }

    /**
     * Does the reading breach this rule? For numeric metrics, compare against the threshold with the
     * rule's comparator; for `offline`, the breach is simply that the server is not online.
     *
     * @param  array<string, int|string>  $metrics  cpu/mem/disk/load ints; status when offline
     */
    public function breached(array $metrics, string $status): bool
    {
        if ($this->metric === 'offline') {
            return $status === 'offline'; // only a truly-down server, not a reachable "warning"
        }

        $value = (float) ($metrics[$this->metric] ?? 0);
        $threshold = (float) $this->threshold;

        return $this->comparator === 'lt' ? $value < $threshold : $value > $threshold;
    }

    /** The reading to record on the incident (the breached value; 0 for offline). Float for `load`. */
    public function reading(array $metrics): float
    {
        return $this->metric === 'offline' ? 0.0 : (float) ($metrics[$this->metric] ?? 0);
    }
}
