<?php

declare(strict_types=1);

use App\Models\InventoryItem;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Support\PortfolioCompare\CrossEnvironmentComparePreviewBuilder;
use App\Support\PortfolioCompare\CrossEnvironmentCompareSelection;
use App\Support\PortfolioCompare\CrossEnvironmentPromotionPreflight;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('classifies ready, blocked, and manual mapping subjects from the compare preview', function (): void {
    $fixture = crossEnvironmentPromotionFixture();

    createPromotionSubject(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Aligned Policy',
        snapshot: ['settings' => [['key' => 'aligned', 'value' => 1]]],
    );
    createPromotionSubject(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Aligned Policy',
        snapshot: ['settings' => [['key' => 'aligned', 'value' => 1]]],
    );

    createPromotionSubject(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Different Policy',
        snapshot: ['settings' => [['key' => 'different', 'value' => 1]]],
    );
    createPromotionSubject(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Different Policy',
        snapshot: ['settings' => [['key' => 'different', 'value' => 2]]],
    );

    createPromotionSubject(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Missing Policy',
        snapshot: ['settings' => [['key' => 'missing', 'value' => 1]]],
    );

    createPromotionSubject(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Manual Mapping Policy',
        snapshot: ['settings' => [['key' => 'manual', 'value' => 1]]],
    );
    createPromotionSubject(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Manual Mapping Policy',
        snapshot: ['settings' => [['key' => 'manual', 'value' => 1]]],
        externalId: 'manual-target-1',
    );
    createPromotionSubject(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Manual Mapping Policy',
        snapshot: ['settings' => [['key' => 'manual', 'value' => 1]]],
        externalId: 'manual-target-2',
    );

    InventoryItem::factory()->create([
        'managed_environment_id' => (int) $fixture['sourceEnvironment']->getKey(),
        'policy_type' => 'deviceConfiguration',
        'display_name' => '   ',
        'external_id' => 'missing-source-identifier',
    ]);

    Policy::factory()->create([
        'managed_environment_id' => (int) $fixture['sourceEnvironment']->getKey(),
        'policy_type' => 'deviceConfiguration',
        'external_id' => 'meta-only-source',
        'display_name' => 'Refresh Required Policy',
        'platform' => 'windows',
    ]);
    InventoryItem::factory()->create([
        'managed_environment_id' => (int) $fixture['sourceEnvironment']->getKey(),
        'policy_type' => 'deviceConfiguration',
        'external_id' => 'meta-only-source',
        'display_name' => 'Refresh Required Policy',
        'meta_jsonb' => ['etag' => 'source-meta-only'],
    ]);

    Policy::factory()->create([
        'managed_environment_id' => (int) $fixture['targetEnvironment']->getKey(),
        'policy_type' => 'deviceConfiguration',
        'external_id' => 'meta-only-target',
        'display_name' => 'Refresh Required Policy',
        'platform' => 'windows',
    ]);
    InventoryItem::factory()->create([
        'managed_environment_id' => (int) $fixture['targetEnvironment']->getKey(),
        'policy_type' => 'deviceConfiguration',
        'external_id' => 'meta-only-target',
        'display_name' => 'Refresh Required Policy',
        'meta_jsonb' => ['etag' => 'target-meta-only'],
    ]);

    $selection = new CrossEnvironmentCompareSelection(
        sourceEnvironment: $fixture['sourceEnvironment'],
        targetEnvironment: $fixture['targetEnvironment'],
        policyTypes: ['deviceConfiguration'],
    );

    $preview = app(CrossEnvironmentComparePreviewBuilder::class)->build($selection);
    $preflight = app(CrossEnvironmentPromotionPreflight::class)->build($preview);

    expect($preflight['summary'])->toBe([
        'ready' => 3,
        'blocked' => 2,
        'manual_mapping_required' => 1,
        'total' => 6,
    ]);

    $bucketByName = collect($preflight['buckets']['ready'])
        ->merge($preflight['buckets']['blocked'])
        ->merge($preflight['buckets']['manual_mapping_required'])
        ->mapWithKeys(static fn (array $subject): array => [
            (string) ($subject['displayName'] ?? '') => $subject['preflight'],
        ]);

    expect(data_get($bucketByName, 'Aligned Policy.bucket'))->toBe('ready')
        ->and(data_get($bucketByName, 'Different Policy.bucket'))->toBe('ready')
        ->and(data_get($bucketByName, 'Missing Policy.bucket'))->toBe('ready')
        ->and(data_get($bucketByName, 'Manual Mapping Policy.bucket'))->toBe('manual_mapping_required')
        ->and(data_get($bucketByName, 'Refresh Required Policy.bucket'))->toBe('blocked');

    $identifierGap = collect($preflight['buckets']['blocked'])
        ->first(fn (array $subject): bool => in_array('source_identifier_missing', data_get($subject, 'preflight.reasonCodes', []), true));

    expect($identifierGap)->toBeArray()
        ->and(data_get($identifierGap, 'preflight.reasonLabels.0'))->toBe('Source environment subject is missing a stable compare identifier.')
        ->and($preflight['blockedReasonCounts'])->toMatchArray([
            'source_identifier_missing' => 1,
            'source_evidence_refresh_required' => 1,
            'target_subject_ambiguous' => 1,
        ]);
});

it('remains read only when building a promotion preflight', function (): void {
    $fixture = crossEnvironmentPromotionFixture();

    createPromotionSubject(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Readonly Policy',
        snapshot: ['settings' => [['key' => 'readonly', 'value' => 1]]],
    );

    $preview = app(CrossEnvironmentComparePreviewBuilder::class)->build(new CrossEnvironmentCompareSelection(
        sourceEnvironment: $fixture['sourceEnvironment'],
        targetEnvironment: $fixture['targetEnvironment'],
        policyTypes: ['deviceConfiguration'],
    ));

    $operationRunCount = OperationRun::query()->count();
    $policyVersionCount = PolicyVersion::query()->count();

    app(CrossEnvironmentPromotionPreflight::class)->build($preview);

    expect(OperationRun::query()->count())->toBe($operationRunCount)
        ->and(PolicyVersion::query()->count())->toBe($policyVersionCount);
});

/**
 * @return array{sourceEnvironment: ManagedEnvironment, targetEnvironment: ManagedEnvironment}
 */
function crossEnvironmentPromotionFixture(): array
{
    $sourceEnvironment = ManagedEnvironment::factory()->create();
    $targetEnvironment = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $sourceEnvironment->workspace_id,
    ]);

    return [
        'sourceEnvironment' => $sourceEnvironment,
        'targetEnvironment' => $targetEnvironment,
    ];
}

/**
 * @param  array<string, mixed>  $snapshot
 * @return array{policy: Policy, version: PolicyVersion, inventory: InventoryItem}
 */
function createPromotionSubject(
    ManagedEnvironment $tenant,
    string $displayName,
    array $snapshot,
    string $policyType = 'deviceConfiguration',
    ?string $externalId = null,
): array {
    $policy = Policy::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'policy_type' => $policyType,
        'external_id' => $externalId ?? str($displayName)->slug()->append('-')->append((string) str()->uuid())->toString(),
        'display_name' => $displayName,
        'platform' => 'windows',
    ]);

    $version = PolicyVersion::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'policy_id' => (int) $policy->getKey(),
        'policy_type' => $policyType,
        'platform' => 'windows',
        'captured_at' => now(),
        'snapshot' => $snapshot,
        'assignments' => [],
        'scope_tags' => [],
    ]);

    $inventory = InventoryItem::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'policy_type' => $policyType,
        'external_id' => (string) $policy->external_id,
        'display_name' => $displayName,
        'last_seen_at' => now(),
    ]);

    return [
        'policy' => $policy,
        'version' => $version,
        'inventory' => $inventory,
    ];
}
