<?php

declare(strict_types=1);

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

uses(RefreshDatabase::class);

it('builds stable compare states for matching, differing, and missing subjects', function (): void {
    $fixture = crossEnvironmentCompareFixture();

    createComparedPolicy(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'WiFi Corp',
        snapshot: ['settings' => [['key' => 'wifi', 'value' => 1]]],
    );
    createComparedPolicy(
        tenant: $fixture['targetEnvironment'],
        displayName: 'WiFi Corp',
        snapshot: ['settings' => [['key' => 'wifi', 'value' => 1]]],
    );

    createComparedPolicy(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Windows Compliance',
        snapshot: ['settings' => [['key' => 'compliance', 'value' => 1]]],
    );
    createComparedPolicy(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Windows Compliance',
        snapshot: ['settings' => [['key' => 'compliance', 'value' => 2]]],
    );

    createComparedPolicy(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'VPN Profile',
        snapshot: ['settings' => [['key' => 'vpn', 'value' => 1]]],
    );

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

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

    expect($preview['summary'])->toBe([
        'match' => 1,
        'different' => 1,
        'missing' => 1,
        'ambiguous' => 0,
        'blocked' => 0,
        'total' => 3,
    ]);

    $subjects = collect($preview['subjects'])->keyBy('displayName');

    expect($subjects->get('WiFi Corp'))->not->toBeNull()
        ->and($subjects->get('WiFi Corp')['state'])->toBe('match')
        ->and(data_get($subjects->get('WiFi Corp'), 'source.evidence.fidelity'))->toBe('content')
        ->and(data_get($subjects->get('WiFi Corp'), 'target.evidence.fidelity'))->toBe('content')
        ->and($subjects->get('Windows Compliance')['state'])->toBe('different')
        ->and($subjects->get('VPN Profile')['state'])->toBe('missing');

    expect($builder->build($selection))->toBe($preview);
});

it('marks unresolved source identity and duplicate target matches distinctly', function (): void {
    $fixture = crossEnvironmentCompareFixture();

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

    createComparedPolicy(
        tenant: $fixture['sourceEnvironment'],
        displayName: 'Duplicated Policy',
        snapshot: ['settings' => [['key' => 'dup', 'value' => 1]]],
    );

    createComparedPolicy(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Duplicated Policy',
        externalId: 'dup-target-1',
        snapshot: ['settings' => [['key' => 'dup', 'value' => 1]]],
    );
    createComparedPolicy(
        tenant: $fixture['targetEnvironment'],
        displayName: 'Duplicated Policy',
        externalId: 'dup-target-2',
        snapshot: ['settings' => [['key' => 'dup', 'value' => 1]]],
    );

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

    expect($preview['summary'])->toBe([
        'match' => 0,
        'different' => 0,
        'missing' => 0,
        'ambiguous' => 1,
        'blocked' => 1,
        'total' => 2,
    ]);

    $identifierGap = collect($preview['subjects'])
        ->first(fn (array $subject): bool => in_array('source_identifier_missing', $subject['reasonCodes'], true));
    $ambiguousTarget = collect($preview['subjects'])
        ->first(fn (array $subject): bool => in_array('target_subject_ambiguous', $subject['reasonCodes'], true));

    expect($identifierGap)->toBeArray()
        ->and($identifierGap['state'])->toBe('blocked')
        ->and($identifierGap['trustLevel'])->toBe('unusable')
        ->and($ambiguousTarget)->toBeArray()
        ->and($ambiguousTarget['state'])->toBe('ambiguous')
        ->and($ambiguousTarget['trustLevel'])->toBe('diagnostic_only');
});

/**
 * @return array{sourceEnvironment: ManagedEnvironment, targetEnvironment: ManagedEnvironment}
 */
function crossEnvironmentCompareFixture(): 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 createComparedPolicy(
    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,
    ];
}
