<?php

declare(strict_types=1);

use App\Support\PortfolioCompare\CrossEnvironmentPromotionExecutionPlanner;

it('builds a ready-only promotion execution plan with stable identity inputs', function (): void {
    $planner = app(CrossEnvironmentPromotionExecutionPlanner::class);

    $preview = [
        'selection' => [
            'sourceEnvironmentId' => 10,
            'targetEnvironmentId' => 20,
            'policyTypes' => ['settingsCatalog', 'deviceConfiguration'],
        ],
    ];

    $preflight = [
        'selection' => [
            'sourceEnvironmentId' => 10,
            'targetEnvironmentId' => 20,
            'policyTypes' => ['deviceConfiguration', 'settingsCatalog'],
        ],
        'buckets' => [
            'ready' => [
                plannerSubject('Aligned Policy', 'aligned-subject', 'deviceConfiguration', 'match', 10, 20, 101, 'hash-aligned'),
                plannerSubject('Create Policy', 'create-subject', 'deviceConfiguration', 'missing', 10, 20, 102, 'hash-create'),
                plannerSubject('Update Policy', 'update-subject', 'settingsCatalog', 'different', 10, 20, 103, 'hash-update'),
                plannerSubject('Missing Version Policy', 'missing-version-subject', 'deviceConfiguration', 'missing', 10, 20, null, null),
            ],
            'blocked' => [
                plannerSubject('Blocked Policy', 'blocked-subject', 'deviceConfiguration', 'different', 10, 20, 104, 'hash-blocked', ['write_gate_blocked']),
            ],
            'manual_mapping_required' => [
                plannerSubject('Manual Policy', 'manual-subject', 'deviceConfiguration', 'different', 10, 20, 105, 'hash-manual', ['target_subject_ambiguous']),
            ],
        ],
    ];

    $plan = $planner->build($preview, $preflight);

    expect($plan['summary'])->toBe([
        'total' => 6,
        'ready' => 3,
        'excluded' => 3,
        'skipped' => 1,
        'created' => 1,
        'updated' => 1,
    ])
        ->and($plan['selection'])->toBe([
            'sourceEnvironmentId' => 10,
            'targetEnvironmentId' => 20,
            'policyTypes' => ['deviceConfiguration', 'settingsCatalog'],
        ])
        ->and(array_column($plan['items'], 'execution_action'))->toBe([
            'skip_aligned',
            'create_missing',
            'update_existing',
        ])
        ->and(array_column($plan['excluded'], 'excluded_reason'))->toContain('blocked', 'manual_mapping_required', 'source_policy_version_missing')
        ->and($plan['identity'])->toMatchArray([
            'source_environment_id' => 10,
            'target_environment_id' => 20,
            'policy_types' => ['deviceConfiguration', 'settingsCatalog'],
        ])
        ->and($plan['identity']['subjects'])->toBe([
            [
                'policy_type' => 'deviceConfiguration',
                'subject_key' => 'aligned-subject',
                'source_policy_version_id' => 101,
                'source_evidence_hash' => 'hash-aligned',
                'target_subject_external_id' => 'target-aligned-subject',
                'execution_action' => 'skip_aligned',
            ],
            [
                'policy_type' => 'deviceConfiguration',
                'subject_key' => 'create-subject',
                'source_policy_version_id' => 102,
                'source_evidence_hash' => 'hash-create',
                'target_subject_external_id' => 'target-create-subject',
                'execution_action' => 'create_missing',
            ],
            [
                'policy_type' => 'settingsCatalog',
                'subject_key' => 'update-subject',
                'source_policy_version_id' => 103,
                'source_evidence_hash' => 'hash-update',
                'target_subject_external_id' => 'target-update-subject',
                'execution_action' => 'update_existing',
            ],
        ]);
});

it('rejects stale promotion preflight selections', function (): void {
    $planner = app(CrossEnvironmentPromotionExecutionPlanner::class);

    expect(fn (): array => $planner->build(
        ['selection' => ['sourceEnvironmentId' => 10, 'targetEnvironmentId' => 20, 'policyTypes' => ['deviceConfiguration']]],
        ['selection' => ['sourceEnvironmentId' => 10, 'targetEnvironmentId' => 21, 'policyTypes' => ['deviceConfiguration']], 'buckets' => ['ready' => [], 'blocked' => [], 'manual_mapping_required' => []]],
    ))->toThrow(InvalidArgumentException::class, 'Promotion preflight is stale. Regenerate the preflight before execution.');
});

it('rejects promotion preflights with no executable ready subjects', function (): void {
    $planner = app(CrossEnvironmentPromotionExecutionPlanner::class);

    expect(fn (): array => $planner->build(
        ['selection' => ['sourceEnvironmentId' => 10, 'targetEnvironmentId' => 20, 'policyTypes' => ['deviceConfiguration']]],
        ['selection' => ['sourceEnvironmentId' => 10, 'targetEnvironmentId' => 20, 'policyTypes' => ['deviceConfiguration']], 'buckets' => ['ready' => [], 'blocked' => [plannerSubject('Blocked Policy', 'blocked-subject', 'deviceConfiguration', 'different', 10, 20, 104, 'hash-blocked')], 'manual_mapping_required' => []]],
    ))->toThrow(DomainException::class, 'Promotion preflight has no executable ready subjects.');
});

/**
 * @param  list<string>  $reasonCodes
 * @return array<string, mixed>
 */
function plannerSubject(
    string $displayName,
    string $subjectKey,
    string $policyType,
    string $state,
    int $sourceEnvironmentId,
    int $targetEnvironmentId,
    ?int $policyVersionId,
    ?string $evidenceHash,
    array $reasonCodes = [],
): array {
    return [
        'displayName' => $displayName,
        'subjectKey' => $subjectKey,
        'policyType' => $policyType,
        'state' => $state,
        'source' => [
            'environmentId' => $sourceEnvironmentId,
            'inventoryItemId' => $policyVersionId !== null ? $policyVersionId + 1000 : null,
            'subjectExternalId' => 'source-'.$subjectKey,
            'evidence' => [
                'policyVersionId' => $policyVersionId,
                'hash' => $evidenceHash,
            ],
        ],
        'target' => [
            'environmentId' => $targetEnvironmentId,
            'inventoryItemId' => $policyVersionId !== null ? $policyVersionId + 2000 : null,
            'subjectExternalId' => 'target-'.$subjectKey,
        ],
        'preflight' => [
            'reasonCodes' => $reasonCodes,
        ],
    ];
}