<?php

declare(strict_types=1);

require_once __DIR__.'/Support/FakeCompareStrategy.php';

use App\Filament\Pages\BaselineCompareMatrix;
use App\Jobs\CompareBaselineToTenantJob;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Services\Baselines\BaselineCompareService;
use App\Support\Baselines\BaselineReasonCodes;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
use App\Support\Baselines\Compare\IntuneCompareStrategy;
use App\Support\Governance\GovernanceSubjectTaxonomyRegistry;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
use Tests\Feature\Baselines\Support\FakeCompareStrategy;
use Tests\Feature\Baselines\Support\FakeGovernanceSubjectTaxonomyRegistry;
use Tests\Feature\Concerns\BuildsBaselineCompareMatrixFixtures;

uses(RefreshDatabase::class, BuildsBaselineCompareMatrixFixtures::class);

it('fans out compare starts across the visible assigned set without creating a workspace umbrella run', function (): void {
    Queue::fake();

    $fixture = $this->makeBaselineCompareMatrixFixture();

    $readonlyTenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $fixture['workspace']->getKey(),
        'name' => 'Readonly Contoso',
    ]);

    $fixture['user']->tenants()->syncWithoutDetaching([
        (int) $readonlyTenant->getKey() => ['role' => 'readonly'],
    ]);

    $this->assignTenantToBaselineProfile($fixture['profile'], $readonlyTenant);

    $service = app(BaselineCompareService::class);

    $existingRunResult = $service->startCompareForProfile(
        $fixture['profile'],
        $fixture['visibleTenantTwo'],
        $fixture['user'],
    );

    expect($existingRunResult['ok'] ?? false)->toBeTrue();

    $result = $service->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);

    expect($result['visibleAssignedTenantCount'])->toBe(3)
        ->and($result['queuedCount'])->toBe(2)
        ->and($result['alreadyQueuedCount'])->toBe(1)
        ->and($result['blockedCount'])->toBe(0);

    $launchStates = collect($result['targets'])
        ->mapWithKeys(static fn (array $target): array => [(int) $target['tenantId'] => (string) $target['launchState']])
        ->all();

    expect($launchStates[(int) $fixture['visibleTenant']->getKey()] ?? null)->toBe('queued')
        ->and($launchStates[(int) $fixture['visibleTenantTwo']->getKey()] ?? null)->toBe('already_queued')
        ->and($launchStates[(int) $readonlyTenant->getKey()] ?? null)->toBe('queued');

    Queue::assertPushed(CompareBaselineToTenantJob::class);

    $activeRuns = OperationRun::query()
        ->where('workspace_id', (int) $fixture['workspace']->getKey())
        ->where('type', OperationRunType::BaselineCompare->value)
        ->get();

    expect($activeRuns)->toHaveCount(3)
        ->and($activeRuns->every(static fn (OperationRun $run): bool => $run->managed_environment_id !== null))->toBeTrue()
        ->and($activeRuns->every(static fn (OperationRun $run): bool => (string) $run->status === OperationRunStatus::Queued->value))->toBeTrue()
        ->and($activeRuns->every(static fn (OperationRun $run): bool => (string) $run->outcome === OperationRunOutcome::Pending->value))->toBeTrue()
        ->and(OperationRun::query()->whereNull('managed_environment_id')->where('type', OperationRunType::BaselineCompare->value)->count())->toBe(0);
});

it('runs compare assigned tenants from the matrix page and keeps feedback on tenant-owned runs', function (): void {
    Queue::fake();

    $fixture = $this->makeBaselineCompareMatrixFixture();

    $this->setAdminWorkspaceContext($fixture['user'], $fixture['workspace']);

    Livewire::actingAs($fixture['user'])
        ->test(BaselineCompareMatrix::class, ['record' => $fixture['profile']->getKey()])
        ->assertActionVisible('compareAssignedTenants')
        ->assertActionEnabled('compareAssignedTenants')
        ->callAction('compareAssignedTenants')
        ->assertStatus(200);

    Queue::assertPushed(CompareBaselineToTenantJob::class, 2);

    expect(OperationRun::query()
        ->where('workspace_id', (int) $fixture['workspace']->getKey())
        ->where('type', OperationRunType::BaselineCompare->value)
        ->whereNull('managed_environment_id')
        ->count())->toBe(0);
});

it('blocks visible assignment fanout when the baseline scope spans multiple compare strategy families', function (): void {
    Queue::fake();

    $fixture = $this->makeBaselineCompareMatrixFixture();

    app()->instance(GovernanceSubjectTaxonomyRegistry::class, new FakeGovernanceSubjectTaxonomyRegistry);
    app()->instance(CompareStrategyRegistry::class, new CompareStrategyRegistry([
        app(IntuneCompareStrategy::class),
        app(FakeCompareStrategy::class),
    ]));

    $fixture['profile']->update([
        'scope_jsonb' => [
            'version' => 2,
            'entries' => [
                [
                    'domain_key' => 'intune',
                    'subject_class' => 'policy',
                    'subject_type_keys' => ['deviceConfiguration'],
                    'filters' => [],
                ],
                [
                    'domain_key' => 'entra',
                    'subject_class' => 'control',
                    'subject_type_keys' => ['conditionalAccessPolicy'],
                    'filters' => [],
                ],
            ],
        ],
    ]);

    $result = app(BaselineCompareService::class)->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);

    expect($result['visibleAssignedTenantCount'])->toBe(2)
        ->and($result['queuedCount'])->toBe(0)
        ->and($result['alreadyQueuedCount'])->toBe(0)
        ->and($result['blockedCount'])->toBe(2)
        ->and(collect($result['targets'])->pluck('reasonCode')->unique()->values()->all())
        ->toBe([BaselineReasonCodes::COMPARE_MIXED_SCOPE]);

    Queue::assertNotPushed(CompareBaselineToTenantJob::class);
});

it('blocks visible assignment fanout when the baseline scope has no compatible compare strategy family', function (): void {
    Queue::fake();

    $fixture = $this->makeBaselineCompareMatrixFixture();

    app()->instance(GovernanceSubjectTaxonomyRegistry::class, new FakeGovernanceSubjectTaxonomyRegistry);
    app()->instance(CompareStrategyRegistry::class, new CompareStrategyRegistry([
        app(IntuneCompareStrategy::class),
    ]));

    $fixture['profile']->update([
        'scope_jsonb' => [
            'version' => 2,
            'entries' => [
                [
                    'domain_key' => 'entra',
                    'subject_class' => 'control',
                    'subject_type_keys' => ['conditionalAccessPolicy'],
                    'filters' => [],
                ],
            ],
        ],
    ]);

    $result = app(BaselineCompareService::class)->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);

    expect($result['visibleAssignedTenantCount'])->toBe(2)
        ->and($result['queuedCount'])->toBe(0)
        ->and($result['alreadyQueuedCount'])->toBe(0)
        ->and($result['blockedCount'])->toBe(2)
        ->and(collect($result['targets'])->pluck('reasonCode')->unique()->values()->all())
        ->toBe([BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE]);

    Queue::assertNotPushed(CompareBaselineToTenantJob::class);
});
