<?php

declare(strict_types=1);

use App\Models\ManagedEnvironment;
use App\Support\Baselines\BaselineScope;
use App\Support\Baselines\Compare\CompareOrchestrationContext;
use App\Support\Baselines\Compare\CompareStrategy;
use App\Support\Baselines\Compare\CompareStrategyCapability;
use App\Support\Baselines\Compare\CompareStrategyKey;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
use App\Support\Governance\GovernanceDomainKey;
use App\Support\Governance\GovernanceSubjectClass;

it('selects a single compatible strategy family for a canonical scope entry', function (): void {
    $registry = new CompareStrategyRegistry([
        compareStrategyStub(
            key: 'intune_policy',
            capabilities: [
                new CompareStrategyCapability(
                    strategyKey: CompareStrategyKey::intunePolicy(),
                    domainKeys: [GovernanceDomainKey::Intune->value, GovernanceDomainKey::PlatformFoundation->value],
                    subjectClasses: [
                        GovernanceSubjectClass::Policy->value,
                        GovernanceSubjectClass::ConfigurationResource->value,
                    ],
                ),
            ],
        ),
    ]);

    $scope = BaselineScope::fromJsonb([
        'version' => 2,
        'entries' => [
            [
                'domain_key' => GovernanceDomainKey::Intune->value,
                'subject_class' => GovernanceSubjectClass::Policy->value,
                'subject_type_keys' => ['deviceConfiguration'],
                'filters' => [],
            ],
            [
                'domain_key' => GovernanceDomainKey::PlatformFoundation->value,
                'subject_class' => GovernanceSubjectClass::ConfigurationResource->value,
                'subject_type_keys' => ['assignmentFilter'],
                'filters' => [],
            ],
        ],
    ]);

    $selection = $registry->select($scope);

    expect($selection->isSupported())->toBeTrue()
        ->and($selection->strategyKey?->value)->toBe('intune_policy')
        ->and($selection->matchedScopeEntries)->toHaveCount(2)
        ->and($selection->rejectedScopeEntries)->toBe([]);
});

it('rejects canonical scope entries when no strategy supports them', function (): void {
    $registry = new CompareStrategyRegistry([
        compareStrategyStub(
            key: 'intune_policy',
            capabilities: [
                new CompareStrategyCapability(
                    strategyKey: CompareStrategyKey::intunePolicy(),
                    domainKeys: [GovernanceDomainKey::Intune->value],
                    subjectClasses: [GovernanceSubjectClass::Policy->value],
                ),
            ],
        ),
    ]);

    $scope = new BaselineScope(
        entries: [[
            'domain_key' => GovernanceDomainKey::Entra->value,
            'subject_class' => GovernanceSubjectClass::Control->value,
            'subject_type_keys' => ['conditionalAccessPolicy'],
            'filters' => [],
        ]],
        version: 2,
    );

    $selection = $registry->select($scope);

    expect($selection->isUnsupported())->toBeTrue()
        ->and($selection->strategyKey)->toBeNull()
        ->and($selection->matchedScopeEntries)->toBe([])
        ->and($selection->rejectedScopeEntries)->toHaveCount(1);
});

it('marks scope as mixed when multiple strategy families are required', function (): void {
    $registry = new CompareStrategyRegistry([
        compareStrategyStub(
            key: 'intune_policy',
            capabilities: [
                new CompareStrategyCapability(
                    strategyKey: CompareStrategyKey::intunePolicy(),
                    domainKeys: [GovernanceDomainKey::Intune->value],
                    subjectClasses: [GovernanceSubjectClass::Policy->value],
                ),
            ],
        ),
        compareStrategyStub(
            key: 'future_control',
            capabilities: [
                new CompareStrategyCapability(
                    strategyKey: CompareStrategyKey::from('future_control'),
                    domainKeys: [GovernanceDomainKey::Entra->value],
                    subjectClasses: [GovernanceSubjectClass::Control->value],
                ),
            ],
        ),
    ]);

    $scope = new BaselineScope(
        entries: [
            [
                'domain_key' => GovernanceDomainKey::Intune->value,
                'subject_class' => GovernanceSubjectClass::Policy->value,
                'subject_type_keys' => ['deviceConfiguration'],
                'filters' => [],
            ],
            [
                'domain_key' => GovernanceDomainKey::Entra->value,
                'subject_class' => GovernanceSubjectClass::Control->value,
                'subject_type_keys' => ['conditionalAccessPolicy'],
                'filters' => [],
            ],
        ],
        version: 2,
    );

    $selection = $registry->select($scope);

    expect($selection->isMixed())->toBeTrue()
        ->and($selection->strategyKey)->toBeNull()
        ->and($selection->matchedScopeEntries)->toHaveCount(2)
        ->and($selection->diagnostics['matched_strategy_keys'] ?? [])->toEqual(['future_control', 'intune_policy']);
});

it('supports deterministic future-domain selection without implicit intune fallback', function (): void {
    $registry = new CompareStrategyRegistry([
        compareStrategyStub(
            key: 'future_control',
            capabilities: [
                new CompareStrategyCapability(
                    strategyKey: CompareStrategyKey::from('future_control'),
                    domainKeys: [GovernanceDomainKey::Entra->value],
                    subjectClasses: [GovernanceSubjectClass::Control->value],
                ),
            ],
        ),
    ]);

    $scope = new BaselineScope(
        entries: [[
            'domain_key' => GovernanceDomainKey::Entra->value,
            'subject_class' => GovernanceSubjectClass::Control->value,
            'subject_type_keys' => ['conditionalAccessPolicy'],
            'filters' => [],
        ]],
        version: 2,
    );

    $selection = $registry->select($scope);

    expect($selection->isSupported())->toBeTrue()
        ->and($selection->strategyKey?->value)->toBe('future_control')
        ->and($registry->resolve('future_control'))->toBeInstanceOf(CompareStrategy::class);
});

it('throws when resolving an unknown strategy key', function (): void {
    $registry = new CompareStrategyRegistry([]);

    expect(fn (): CompareStrategy => $registry->resolve('missing_strategy'))
        ->toThrow(InvalidArgumentException::class, 'Unknown compare strategy');
});

/**
 * @param  list<CompareStrategyCapability>  $capabilities
 */
function compareStrategyStub(string $key, array $capabilities): CompareStrategy
{
    return new class($key, $capabilities) implements CompareStrategy
    {
        /**
         * @param  list<CompareStrategyCapability>  $capabilities
         */
        public function __construct(
            private readonly string $keyValue,
            private readonly array $capabilities,
        ) {}

        public function key(): CompareStrategyKey
        {
            return CompareStrategyKey::from($this->keyValue);
        }

        public function capabilities(): array
        {
            return $this->capabilities;
        }

        public function compare(
            CompareOrchestrationContext $context,
            ManagedEnvironment $tenant,
            array $baselineItems,
            array $currentItems,
            array $resolvedCurrentEvidence,
            array $severityMapping,
        ): array {
            return [
                'subject_results' => [],
                'diagnostics' => [],
            ];
        }
    };
}