<?php

require_once dirname(__DIR__).'/Baselines/Support/FakeCompareStrategy.php';

use App\Filament\Resources\BaselineProfileResource;
use App\Filament\Resources\BaselineProfileResource\Pages\ViewBaselineProfile;
use App\Jobs\CompareBaselineToTenantJob;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
use App\Support\Baselines\Compare\IntuneCompareStrategy;
use App\Support\Governance\GovernanceSubjectTaxonomyRegistry;
use App\Support\OperationRunType;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Livewire\Features\SupportTesting\Testable;
use Livewire\Livewire;
use Tests\Feature\Baselines\Support\FakeCompareStrategy;
use Tests\Feature\Baselines\Support\FakeGovernanceSubjectTaxonomyRegistry;

function baselineProfileHeaderActions(Testable $component): array
{
    $instance = $component->instance();

    if ($instance->getCachedHeaderActions() === []) {
        $instance->cacheInteractsWithHeaderActions();
    }

    return $instance->getCachedHeaderActions();
}

/**
 * @return array{0: BaselineProfile, 1: BaselineSnapshot}
 */
function seedComparableBaselineProfileForTenant(ManagedEnvironment $tenant, BaselineCaptureMode $captureMode = BaselineCaptureMode::FullContent): array
{
    [$profile, $snapshot] = seedActiveBaselineForTenant($tenant);

    $profile->forceFill([
        'capture_mode' => $captureMode->value,
    ])->save();

    return [$profile->fresh(), $snapshot];
}

it('does not start baseline compare for workspace members missing tenant.sync', function (): void {
    Queue::fake();
    config()->set('tenantpilot.baselines.full_content_capture.enabled', true);

    [$user, $tenant] = createUserWithTenant(role: 'readonly');

    [$profile] = seedComparableBaselineProfileForTenant($tenant);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertActionVisible('compareNow')
        ->assertActionHasLabel('compareNow', 'Compare now (full content)')
        ->assertActionDisabled('compareNow')
        ->callAction('compareNow', data: ['target_environment_id' => (int) $tenant->getKey()])
        ->assertStatus(200);

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

it('starts baseline compare successfully for authorized workspace members', function (): void {
    Queue::fake();
    config()->set('tenantpilot.baselines.full_content_capture.enabled', true);

    [$user, $tenant] = createUserWithTenant(role: 'owner');

    [$profile] = seedComparableBaselineProfileForTenant($tenant);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertActionVisible('compareNow')
        ->assertActionHasLabel('compareNow', 'Compare now (full content)')
        ->assertActionEnabled('compareNow')
        ->callAction('compareNow', data: ['target_environment_id' => (int) $tenant->getKey()])
        ->assertStatus(200);

    Queue::assertPushed(CompareBaselineToTenantJob::class);

    $run = OperationRun::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('type', OperationRunType::BaselineCompare->value)
        ->latest('id')
        ->first();

    expect($run)->not->toBeNull();
    expect($run?->status)->toBe('queued');
});

it('does not start full-content baseline compare when rollout is disabled', function (): void {
    Queue::fake();
    config()->set('tenantpilot.baselines.full_content_capture.enabled', false);

    [$user, $tenant] = createUserWithTenant(role: 'owner');

    [$profile] = seedComparableBaselineProfileForTenant($tenant);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertActionVisible('compareNow')
        ->assertActionHasLabel('compareNow', 'Compare now (full content)')
        ->assertActionEnabled('compareNow')
        ->callAction('compareNow', data: ['target_environment_id' => (int) $tenant->getKey()])
        ->assertNotified('Cannot start comparison')
        ->assertStatus(200);

    Queue::assertNotPushed(CompareBaselineToTenantJob::class);
    expect(OperationRun::query()->where('type', OperationRunType::BaselineCompare->value)->count())->toBe(0);
});

it('shows mixed-strategy compare rejection truth on the workspace start surface', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic);

    $profile->forceFill([
        'workspace_id' => (int) $tenant->workspace_id,
        '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' => [],
                ],
            ],
        ],
    ])->save();

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertSee('Mixed strategy scope')
        ->callAction('compareNow', data: ['target_environment_id' => (int) $tenant->getKey()])
        ->assertNotified('Cannot start comparison')
        ->assertStatus(200);

    Queue::assertNotPushed(CompareBaselineToTenantJob::class);
    expect(OperationRun::query()->where('type', OperationRunType::BaselineCompare->value)->count())->toBe(0);
});

it('moves compare-matrix navigation into related context while keeping compare-assigned-tenants secondary', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $this->actingAs($user);

    [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    $component = Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getLabel() === 'Compare assigned environments'
            && $action->isConfirmationRequired()
            && str_contains((string) $action->getModalDescription(), 'Simulation only.'));

    $topLevelActionNames = collect(baselineProfileHeaderActions($component))
        ->reject(static fn ($action): bool => $action instanceof ActionGroup)
        ->filter(static fn ($action): bool => ! method_exists($action, 'isVisible') || $action->isVisible())
        ->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
        ->filter()
        ->values()
        ->all();
    $moreGroup = collect(baselineProfileHeaderActions($component))
        ->first(static fn ($action): bool => $action instanceof ActionGroup && $action->isVisible());
    $moreActionNames = collect($moreGroup?->getActions() ?? [])
        ->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
        ->filter()
        ->values()
        ->all();

    expect($topLevelActionNames)->toBe(['compareNow'])
        ->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
        ->and($moreActionNames)->toEqualCanonicalizing(['compareAssignedTenants', 'edit'])
        ->and(collect(BaselineProfileResource::detailRelatedContextEntries($profile))->pluck('key')->all())
        ->toContain('compare_matrix', 'baseline_snapshot');
});

it('keeps compare-assigned-tenants visible but disabled for readonly workspace members after the navigation move', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'readonly');

    [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertActionVisible('compareAssignedTenants')
        ->assertActionDisabled('compareAssignedTenants');

    expect(collect(BaselineProfileResource::detailRelatedContextEntries($profile))->pluck('key')->all())
        ->toContain('compare_matrix');
});

it('shows readiness copy without exposing raw canonical scope json on the compare start surface', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic);

    $profile->forceFill([
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ])->save();

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertSee('Support readiness')
        ->assertSee('Capture: ready. Compare: ready.')
        ->assertDontSee('subject_type_keys')
        ->assertDontSee('canonical_scope');
});

it('does not start baseline compare when the stored canonical scope is invalid', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant(role: 'owner');

    [$profile] = seedComparableBaselineProfileForTenant($tenant, BaselineCaptureMode::Opportunistic);

    DB::table('baseline_profiles')
        ->where('id', (int) $profile->getKey())
        ->update([
            'scope_jsonb' => json_encode([
                'version' => 2,
                'entries' => [
                    [
                        'domain_key' => 'platform_foundation',
                        'subject_class' => 'configuration_resource',
                        'subject_type_keys' => ['intuneRoleAssignment'],
                        'filters' => [],
                    ],
                ],
            ], JSON_THROW_ON_ERROR),
            'updated_at' => now(),
        ]);

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::actingAs($user)
        ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
        ->assertActionVisible('compareNow')
        ->callAction('compareNow', data: ['target_environment_id' => (int) $tenant->getKey()])
        ->assertNotified('Cannot start comparison')
        ->assertStatus(200);

    Queue::assertNotPushed(CompareBaselineToTenantJob::class);
    expect(OperationRun::query()->where('type', OperationRunType::BaselineCompare->value)->count())->toBe(0);
});
