<?php

namespace App\Models;

use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
 * A disruption, written down.
 *
 * The point of the record is that it outlives the disruption. "Alle Dienste in
 * Betrieb" the morning after an outage is true and useless to somebody who was
 * locked out the evening before; what answers them is the timeline — when it
 * was noticed, what it turned out to be, when it was fixed.
 */
class Incident extends Model
{
    /** @use HasFactory<\Database\Factories\IncidentFactory> */
    use HasFactory, HasUuid;

    /** How the incident colours the components it touches. */
    public const IMPACT_DEGRADED = 'degraded';

    public const IMPACT_DOWN = 'down';

    public const IMPACT_MAINTENANCE = 'maintenance';

    /** The stages of an update, in the order they normally happen. */
    public const STATUSES = ['investigating', 'identified', 'monitoring', 'resolved'];

    protected $fillable = [
        'title', 'impact', 'components', 'started_at', 'resolved_at', 'published_at', 'created_by',
    ];

    protected function casts(): array
    {
        return [
            'components' => 'array',
            'started_at' => 'datetime',
            'resolved_at' => 'datetime',
            'published_at' => 'datetime',
        ];
    }

    public function updates(): HasMany
    {
        return $this->hasMany(IncidentUpdate::class)->orderByDesc('created_at')->orderByDesc('id');
    }

    /** Only what a visitor may see. Drafts are working notes, not statements. */
    public function scopePublished(Builder $query): Builder
    {
        return $query->whereNotNull('published_at');
    }

    public function scopeOngoing(Builder $query): Builder
    {
        return $query->whereNull('resolved_at');
    }

    public function isResolved(): bool
    {
        return $this->resolved_at !== null;
    }

    public function isPublished(): bool
    {
        return $this->published_at !== null;
    }

    /**
     * The stage the incident is at, taken from its newest update.
     *
     * Derived rather than stored: a stored column and an update log are two
     * records of the same fact, and they drift the first time somebody posts
     * an update without touching the column.
     */
    public function stage(): string
    {
        if ($this->isResolved()) {
            return 'resolved';
        }

        return $this->updates->first()?->status ?? 'investigating';
    }

    /**
     * How long it lasted, in whole minutes; null while it is still running.
     *
     * Start first, end second. diffInMinutes is SIGNED — written the other way
     * round it produced "Dauer -86 Minuten" on the public page, which is the
     * kind of detail that costs a status page the only thing it has.
     */
    public function durationMinutes(): ?int
    {
        return $this->resolved_at === null
            ? null
            : (int) $this->started_at->diffInMinutes($this->resolved_at);
    }
}
