<?php

declare(strict_types=1);

namespace App\Models;

use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Baselines\BaselineProfileStatus;
use App\Support\Baselines\BaselineScope;
use App\Support\Baselines\BaselineSnapshotLifecycleState;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use InvalidArgumentException;
use JsonException;

class BaselineProfile extends Model
{
    use HasFactory;

    /** @var list<string> */
    protected $fillable = [
        'workspace_id',
        'name',
        'description',
        'version_label',
        'status',
        'capture_mode',
        'scope_jsonb',
        'active_snapshot_id',
        'created_by_user_id',
    ];

    /**
     * @return array<string, mixed>
     */
    protected function casts(): array
    {
        return [
            'status' => BaselineProfileStatus::class,
            'capture_mode' => BaselineCaptureMode::class,
        ];
    }

    protected function scopeJsonb(): Attribute
    {
        return Attribute::make(
            get: function (mixed $value): array {
                try {
                    return $this->normalizedScopeFrom($value)->toJsonb();
                } catch (InvalidArgumentException) {
                    return $this->decodeScopeJsonb($value) ?? ['policy_types' => [], 'foundation_types' => []];
                }
            },
            set: function (mixed $value): string {
                $scope = BaselineScope::fromJsonb(is_array($value) ? $value : null)->toStoredJsonb();

                try {
                    return json_encode($scope, JSON_THROW_ON_ERROR);
                } catch (JsonException) {
                    return '{"version":2,"entries":[]}';
                }
            },
        );
    }

    public function normalizedScope(): BaselineScope
    {
        return $this->normalizedScopeFrom($this->getRawOriginal('scope_jsonb'));
    }

    /**
     * @return array<string, mixed>|null
     */
    public function rawScopeJsonb(): ?array
    {
        return $this->decodeScopeJsonb($this->getRawOriginal('scope_jsonb'));
    }

    /**
     * @return array{version: 2, entries: list<array{domain_key: string, subject_class: string, subject_type_keys: list<string>, filters: array<string, mixed>}>}
     */
    public function canonicalScopeJsonb(): array
    {
        return $this->normalizedScope()->toStoredJsonb();
    }

    public function requiresScopeSaveForward(): bool
    {
        return (bool) $this->normalizedScope()->normalizationLineage()['save_forward_required'];
    }

    public function rewriteScopeToCanonicalV2(): bool
    {
        $this->scope_jsonb = $this->canonicalScopeJsonb();

        if (! $this->isDirty('scope_jsonb')) {
            return false;
        }

        return $this->save();
    }

    /**
     * Decode raw scope_jsonb value from the database.
     *
     * @return array<string, mixed>|null
     */
    private function decodeScopeJsonb(mixed $value): ?array
    {
        if (is_array($value)) {
            return $value;
        }

        if (is_string($value) && $value !== '') {
            try {
                $decoded = json_decode($value, true, flags: JSON_THROW_ON_ERROR);

                return is_array($decoded) ? $decoded : null;
            } catch (JsonException) {
                return null;
            }
        }

        return null;
    }

    private function normalizedScopeFrom(mixed $value): BaselineScope
    {
        return BaselineScope::fromJsonb($this->decodeScopeJsonb($value));
    }

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

    public function createdByUser(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by_user_id');
    }

    public function activeSnapshot(): BelongsTo
    {
        return $this->belongsTo(BaselineSnapshot::class, 'active_snapshot_id');
    }

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

    public function resolveCurrentConsumableSnapshot(): ?BaselineSnapshot
    {
        $activeSnapshot = $this->relationLoaded('activeSnapshot')
            ? $this->getRelation('activeSnapshot')
            : $this->activeSnapshot()->first();

        if ($activeSnapshot instanceof BaselineSnapshot && $activeSnapshot->isConsumable()) {
            return $activeSnapshot;
        }

        return $this->snapshots()
            ->where('lifecycle_state', BaselineSnapshotLifecycleState::Complete->value)
            ->orderByDesc('completed_at')
            ->orderByDesc('captured_at')
            ->orderByDesc('id')
            ->first();
    }

    public function resolveLatestAttemptedSnapshot(): ?BaselineSnapshot
    {
        return $this->snapshots()
            ->orderByDesc('captured_at')
            ->orderByDesc('id')
            ->first();
    }

    public function hasConsumableSnapshot(): bool
    {
        return $this->resolveCurrentConsumableSnapshot() instanceof BaselineSnapshot;
    }

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