<?php

declare(strict_types=1);

namespace App\Models;

use App\Support\Baselines\BaselineReasonCodes;
use App\Support\Baselines\BaselineSnapshotLifecycleState;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use RuntimeException;

class BaselineSnapshot extends Model
{
    use HasFactory;

    protected $guarded = [];

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'lifecycle_state' => BaselineSnapshotLifecycleState::class,
            'summary_jsonb' => 'array',
            'completion_meta_jsonb' => 'array',
            'captured_at' => 'datetime',
            'completed_at' => 'datetime',
            'failed_at' => 'datetime',
        ];
    }

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

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

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

    public function scopeConsumable(Builder $query): Builder
    {
        return $query->where('lifecycle_state', BaselineSnapshotLifecycleState::Complete->value);
    }

    public function scopeLatestConsumable(Builder $query): Builder
    {
        return $query
            ->consumable()
            ->orderByDesc('completed_at')
            ->orderByDesc('captured_at')
            ->orderByDesc('id');
    }

    public function isConsumable(): bool
    {
        return $this->lifecycleState() === BaselineSnapshotLifecycleState::Complete;
    }

    public function isBuilding(): bool
    {
        return $this->lifecycleState() === BaselineSnapshotLifecycleState::Building;
    }

    public function isComplete(): bool
    {
        return $this->lifecycleState() === BaselineSnapshotLifecycleState::Complete;
    }

    public function isIncomplete(): bool
    {
        return $this->lifecycleState() === BaselineSnapshotLifecycleState::Incomplete;
    }

    public function markBuilding(array $completionMeta = []): void
    {
        $this->forceFill([
            'lifecycle_state' => BaselineSnapshotLifecycleState::Building,
            'completed_at' => null,
            'failed_at' => null,
            'completion_meta_jsonb' => $this->mergedCompletionMeta($completionMeta),
        ])->save();
    }

    public function markComplete(string $identityHash, array $completionMeta = []): void
    {
        if ($this->isIncomplete()) {
            throw new RuntimeException('Incomplete baseline snapshots cannot transition back to complete.');
        }

        $this->forceFill([
            'snapshot_identity_hash' => $identityHash,
            'lifecycle_state' => BaselineSnapshotLifecycleState::Complete,
            'completed_at' => now(),
            'failed_at' => null,
            'completion_meta_jsonb' => $this->mergedCompletionMeta($completionMeta),
        ])->save();
    }

    public function markIncomplete(?string $reasonCode = null, array $completionMeta = []): void
    {
        $this->forceFill([
            'lifecycle_state' => BaselineSnapshotLifecycleState::Incomplete,
            'completed_at' => null,
            'failed_at' => now(),
            'completion_meta_jsonb' => $this->mergedCompletionMeta(array_filter([
                'finalization_reason_code' => $reasonCode ?? BaselineReasonCodes::SNAPSHOT_INCOMPLETE,
                ...$completionMeta,
            ], static fn (mixed $value): bool => $value !== null)),
        ])->save();
    }

    public function lifecycleState(): BaselineSnapshotLifecycleState
    {
        if ($this->lifecycle_state instanceof BaselineSnapshotLifecycleState) {
            return $this->lifecycle_state;
        }

        if (is_string($this->lifecycle_state) && BaselineSnapshotLifecycleState::tryFrom($this->lifecycle_state) instanceof BaselineSnapshotLifecycleState) {
            return BaselineSnapshotLifecycleState::from($this->lifecycle_state);
        }

        return BaselineSnapshotLifecycleState::Incomplete;
    }

    /**
     * @param  array<string, mixed>  $completionMeta
     * @return array<string, mixed>
     */
    private function mergedCompletionMeta(array $completionMeta): array
    {
        $existing = is_array($this->completion_meta_jsonb) ? $this->completion_meta_jsonb : [];

        return array_replace($existing, $completionMeta);
    }
}
