<?php

namespace App\Models;

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

class ProvisioningRun extends Model
{
    /** @use HasFactory<\Database\Factories\ProvisioningRunFactory> */
    use HasFactory, HasUuid;

    public const STATUS_PENDING = 'pending';
    public const STATUS_RUNNING = 'running';
    public const STATUS_WAITING = 'waiting';
    public const STATUS_PAUSED = 'paused';
    public const STATUS_COMPLETED = 'completed';
    public const STATUS_FAILED = 'failed';

    protected $fillable = [
        'subject_type', 'subject_id', 'pipeline', 'current_step', 'status',
        'attempt', 'max_attempts', 'next_attempt_at', 'started_at', 'finished_at',
        'error', 'context',
    ];

    protected function casts(): array
    {
        return [
            'context' => 'array',
            'next_attempt_at' => 'datetime',
            'started_at' => 'datetime',
            'finished_at' => 'datetime',
            'current_step' => 'integer',
            'attempt' => 'integer',
            'max_attempts' => 'integer',
        ];
    }

    public function subject(): MorphTo
    {
        return $this->morphTo();
    }

    /**
     * Runs that have not finished one way or the other.
     *
     * The four statuses that mean "this will do more work" were written out at
     * four call sites that ask the same question — and a fifth status added to the
     * list above would have had to be remembered at every one of them. What the
     * callers do with the answer differs, and App\Provisioning\WorkInFlight is
     * where the difference is explained.
     *
     * @param  \Illuminate\Database\Eloquent\Builder<self>  $query
     */
    public function scopeInFlight($query): void
    {
        $query->whereIn('status', [
            self::STATUS_PENDING,
            self::STATUS_RUNNING,
            self::STATUS_WAITING,
            self::STATUS_PAUSED,
        ]);
    }

    public function events(): HasMany
    {
        return $this->hasMany(ProvisioningStepEvent::class, 'run_id');
    }

    public function resources(): HasMany
    {
        return $this->hasMany(RunResource::class, 'run_id');
    }

    /** Read a single key from the json context. */
    public function context(string $key, mixed $default = null): mixed
    {
        return data_get($this->context, $key, $default);
    }

    /** Merge values into the json context and persist. */
    public function mergeContext(array $values): void
    {
        $this->context = array_merge($this->context ?? [], $values);
        $this->save();
    }

    /** Remove a key from the json context and persist (e.g. scrubbing a secret). */
    public function forgetContext(string $key): void
    {
        $context = $this->context ?? [];
        unset($context[$key]);
        $this->context = $context;
        $this->save();
    }
}
