<?php

declare(strict_types=1);

use App\Support\Ui\DerivedState\DerivedStateFamily;
use App\Support\Ui\DerivedState\DerivedStateKey;
use App\Support\Ui\DerivedState\RequestScopedDerivedStateStore;
use Illuminate\Database\Eloquent\Model;

it('builds stable key fingerprints for equivalent context payloads', function (): void {
    $record = new class extends Model
    {
        protected $guarded = [];

        public $timestamps = false;
    };

    $record->forceFill([
        'id' => 42,
        'workspace_id' => 12,
        'managed_environment_id' => 8,
    ]);

    $left = DerivedStateKey::fromModel(
        DerivedStateFamily::ArtifactTruth,
        $record,
        'environment_review',
        [
            'user_id' => 7,
            'scope' => ['tenant' => 8, 'workspace' => 12],
        ],
    );

    $right = DerivedStateKey::fromModel(
        DerivedStateFamily::ArtifactTruth,
        $record,
        'environment_review',
        [
            'scope' => ['workspace' => 12, 'tenant' => 8],
            'user_id' => 7,
        ],
    );

    expect($left->fingerprint())->toBe($right->fingerprint())
        ->and($left->workspaceId)->toBe(12)
        ->and($left->tenantId)->toBe(8);
});

it('reuses cached values after the first miss', function (): void {
    $store = new RequestScopedDerivedStateStore('request-a');
    $key = new DerivedStateKey(DerivedStateFamily::ArtifactTruth, 'App\\Models\\EnvironmentReview', '55', 'environment_review');
    $resolutions = 0;

    $first = $store->resolve($key, function () use (&$resolutions): string {
        $resolutions++;

        return 'derived-result';
    });

    $second = $store->resolve($key, function () use (&$resolutions): string {
        $resolutions++;

        return 'unexpected-second-resolution';
    });

    $record = $store->resolutionRecord($key);

    expect($first)->toBe('derived-result')
        ->and($second)->toBe('derived-result')
        ->and($resolutions)->toBe(1)
        ->and($record)->not->toBeNull()
        ->and($record['negative_result'])->toBeFalse()
        ->and($record['resolved_at'])->toBe(1);
});

it('reuses deterministic negative results when the family allows it', function (): void {
    $store = new RequestScopedDerivedStateStore('request-b');
    $key = new DerivedStateKey(DerivedStateFamily::RelatedNavigationPrimary, 'App\\Models\\Finding', '91', 'finding');
    $resolutions = 0;

    $first = $store->resolve($key, function () use (&$resolutions): ?string {
        $resolutions++;

        return null;
    });

    $second = $store->resolve($key, function () use (&$resolutions): string {
        $resolutions++;

        return 'should-not-run';
    });

    expect($first)->toBeNull()
        ->and($second)->toBeNull()
        ->and($resolutions)->toBe(1)
        ->and($store->resolutionRecord($key)['negative_result'])->toBeTrue();
});

it('keeps variants isolated for the same family and record', function (): void {
    $store = new RequestScopedDerivedStateStore('request-c');
    $resolutions = 0;

    $environmentReviewKey = new DerivedStateKey(DerivedStateFamily::ArtifactTruth, 'App\\Models\\EnvironmentReview', '101', 'environment_review');
    $reviewPackKey = new DerivedStateKey(DerivedStateFamily::ArtifactTruth, 'App\\Models\\EnvironmentReview', '101', 'review_pack');

    $environmentReviewValue = $store->resolve($environmentReviewKey, function () use (&$resolutions): string {
        $resolutions++;

        return 'environment-review';
    });

    $reviewPackValue = $store->resolve($reviewPackKey, function () use (&$resolutions): string {
        $resolutions++;

        return 'review-pack';
    });

    expect($environmentReviewValue)->toBe('environment-review')
        ->and($reviewPackValue)->toBe('review-pack')
        ->and($resolutions)->toBe(2)
        ->and($store->entryCount())->toBe(2);
});

it('invalidates exact keys and whole family slices', function (): void {
    $store = new RequestScopedDerivedStateStore('request-d');
    $environmentReviewKey = new DerivedStateKey(DerivedStateFamily::ArtifactTruth, 'App\\Models\\EnvironmentReview', '1', 'environment_review');
    $reviewPackKey = new DerivedStateKey(DerivedStateFamily::ArtifactTruth, 'App\\Models\\ReviewPack', '9', 'review_pack');
    $navigationKey = new DerivedStateKey(DerivedStateFamily::RelatedNavigationPrimary, 'App\\Models\\Finding', '1', 'finding');

    $store->resolve($environmentReviewKey, static fn (): string => 'review');
    $store->resolve($reviewPackKey, static fn (): string => 'pack');
    $store->resolve($navigationKey, static fn (): string => 'link');

    expect($store->invalidateKey($environmentReviewKey))->toBe(1)
        ->and($store->resolutionRecord($environmentReviewKey))->toBeNull()
        ->and($store->entryCount())->toBe(2)
        ->and($store->invalidateFamily(DerivedStateFamily::ArtifactTruth))->toBe(1)
        ->and($store->entryCount())->toBe(1)
        ->and($store->countStored(DerivedStateFamily::RelatedNavigationPrimary))->toBe(1);
});

it('supports no-reuse and fresh-resolution paths for mutation-sensitive reads', function (): void {
    $store = new RequestScopedDerivedStateStore('request-e');
    $key = new DerivedStateKey(DerivedStateFamily::OperationUxGuidance, 'App\\Models\\OperationRun', '44', 'surface_guidance');
    $resolutions = 0;

    $first = $store->resolve($key, function () use (&$resolutions): string {
        $resolutions++;

        return 'queued';
    }, RequestScopedDerivedStateStore::FRESHNESS_NO_REUSE);

    $second = $store->resolve($key, function () use (&$resolutions): string {
        $resolutions++;

        return 'running';
    }, RequestScopedDerivedStateStore::FRESHNESS_NO_REUSE);

    $store->resolve($key, static fn (): string => 'stale');
    $fresh = $store->resolveFresh($key, static fn (): string => 'fresh');

    expect($first)->toBe('queued')
        ->and($second)->toBe('running')
        ->and($fresh)->toBe('fresh')
        ->and($resolutions)->toBe(2)
        ->and($store->countStored(DerivedStateFamily::OperationUxGuidance, 'App\\Models\\OperationRun', '44', 'surface_guidance'))->toBe(1)
        ->and($store->invalidations())->toHaveCount(1);
});
