<?php

declare(strict_types=1);

use App\Filament\Pages\CrossEnvironmentComparePage;
use App\Filament\Resources\ManagedEnvironmentResource;
use App\Jobs\Operations\CrossEnvironmentPromotionExecutionJob;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\RestoreSafety\RestoreResultAttention;
use App\Support\Tenants\TenantRecoveryTriagePresentation;
use Filament\Actions\Action;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
use Tests\Feature\Concerns\BuildsPortfolioCompareFixtures;
use Tests\Feature\Concerns\BuildsPortfolioTriageFixtures;

uses(RefreshDatabase::class, BuildsPortfolioTriageFixtures::class, BuildsPortfolioCompareFixtures::class);

function crossEnvironmentCompareLaunchQuery(string $url): array
{
    parse_str((string) parse_url($url, PHP_URL_QUERY), $query);

    return $query;
}

it('launches cross-environment compare from the tenant registry with target prefill and return context', function (): void {
    [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
    $targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');

    $backupSet = $this->seedPortfolioBackupConcern($targetEnvironment, TenantBackupHealthAssessment::POSTURE_STALE);
    $this->seedPortfolioRecoveryConcern($targetEnvironment, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, $backupSet);

    $triageState = $this->portfolioReturnFilters(
        [TenantBackupHealthAssessment::POSTURE_STALE],
        [TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED],
        [],
        TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
    );

    $expectedUrl = ManagedEnvironmentResource::crossEnvironmentCompareOpenUrl($targetEnvironment, $triageState);

    $this->portfolioTriageRegistryList($user, $anchorTenant, $triageState)
        ->assertTableActionVisible('compareEnvironments', $targetEnvironment)
        ->assertTableActionHasUrl('compareEnvironments', $expectedUrl, $targetEnvironment);

    $query = crossEnvironmentCompareLaunchQuery($expectedUrl);
    $backUrl = urldecode((string) data_get($query, 'nav.back_url'));

    expect($query)->toMatchArray([
        'target_environment_id' => (string) $targetEnvironment->getKey(),
    ])
        ->and(data_get($query, 'nav.source_surface'))->toBe('tenant_registry')
        ->and(data_get($query, 'nav.environment_id'))->toBe((string) $targetEnvironment->getKey())
        ->and(data_get($query, 'nav.back_label'))->toBe(__('localization.shell.back_to_environment_registry'))
        ->and($backUrl)->toContain('backup_posture[0]='.TenantBackupHealthAssessment::POSTURE_STALE)
        ->and($backUrl)->toContain('recovery_evidence[0]='.TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED)
        ->and($backUrl)->toContain('triage_sort='.TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST);

    Livewire::withQueryParams($query)
        ->actingAs($user)
        ->test(CrossEnvironmentComparePage::class)
        ->assertSet('sourceEnvironmentId', null)
        ->assertSet('targetEnvironmentId', (string) $targetEnvironment->getKey())
        ->assertActionVisible('return_to_origin')
        ->assertActionExists('return_to_origin', fn (Action $action): bool => $action->getLabel() === __('localization.shell.back_to_environment_registry')
            && $action->getUrl() === ManagedEnvironmentResource::getUrl(panel: 'admin', parameters: $triageState));
});

it('launches cross-environment compare from an exact-two bulk selection with both tenants prefilled', function (): void {
    [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
    $targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');

    $anchorBackupSet = $this->seedPortfolioBackupConcern($anchorTenant, TenantBackupHealthAssessment::POSTURE_STALE);
    $this->seedPortfolioRecoveryConcern($anchorTenant, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, $anchorBackupSet);

    $backupSet = $this->seedPortfolioBackupConcern($targetEnvironment, TenantBackupHealthAssessment::POSTURE_STALE);
    $this->seedPortfolioRecoveryConcern($targetEnvironment, RestoreResultAttention::STATE_COMPLETED_WITH_FOLLOW_UP, $backupSet);

    $triageState = $this->portfolioReturnFilters(
        [TenantBackupHealthAssessment::POSTURE_STALE],
        [TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED],
        [],
        TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
    );

    $expectedUrl = ManagedEnvironmentResource::crossEnvironmentCompareOpenUrlForSelection(
        targetEnvironment: $targetEnvironment,
        triageState: $triageState,
        sourceEnvironment: $anchorTenant,
    );

    $this->portfolioTriageRegistryList($user, $anchorTenant, $triageState)
        ->selectTableRecords([$anchorTenant, $targetEnvironment])
        ->assertTableBulkActionVisible('compareSelected')
        ->callTableBulkAction('compareSelected', [$anchorTenant, $targetEnvironment])
        ->assertRedirect($expectedUrl);

    $query = crossEnvironmentCompareLaunchQuery($expectedUrl);
    $backUrl = urldecode((string) data_get($query, 'nav.back_url'));

    expect($query)->toMatchArray([
        'source_environment_id' => (string) $anchorTenant->getKey(),
        'target_environment_id' => (string) $targetEnvironment->getKey(),
    ])
        ->and(data_get($query, 'nav.source_surface'))->toBe('tenant_registry')
        ->and(data_get($query, 'nav.back_label'))->toBe(__('localization.shell.back_to_environment_registry'))
        ->and(data_get($query, 'nav.managed_environment_id'))->toBeNull()
        ->and($backUrl)->toContain('backup_posture[0]='.TenantBackupHealthAssessment::POSTURE_STALE)
        ->and($backUrl)->toContain('recovery_evidence[0]='.TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED)
        ->and($backUrl)->toContain('triage_sort='.TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST);

    Livewire::withQueryParams($query)
        ->actingAs($user)
        ->test(CrossEnvironmentComparePage::class)
        ->assertSet('sourceEnvironmentId', (string) $anchorTenant->getKey())
        ->assertSet('targetEnvironmentId', (string) $targetEnvironment->getKey())
        ->assertActionVisible('return_to_origin');
});

it('keeps launch context after queueing promotion from an exact-two registry launch', function (): void {
    Queue::fake();

    [$user, $anchorTenant] = $this->makePortfolioTriageActor(
        tenantName: 'Anchor ManagedEnvironment',
        workspaceRole: 'owner',
    );
    $targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
    createMinimalUserWithTenant(
        tenant: $targetEnvironment,
        user: $user,
        role: 'owner',
        workspaceRole: 'owner',
    );

    $this->createPortfolioCompareSubject(
        tenant: $anchorTenant,
        displayName: 'Queued Launch Context Policy',
        snapshot: ['settings' => [['key' => 'launch-context', 'value' => 1]]],
    );

    $triageState = $this->portfolioReturnFilters(
        [TenantBackupHealthAssessment::POSTURE_STALE],
        [TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED],
        [],
        TenantRecoveryTriagePresentation::TRIAGE_SORT_WORST_FIRST,
    );

    $expectedUrl = ManagedEnvironmentResource::crossEnvironmentCompareOpenUrlForSelection(
        targetEnvironment: $targetEnvironment,
        triageState: $triageState,
        sourceEnvironment: $anchorTenant,
    );
    $expectedBackUrl = ManagedEnvironmentResource::getUrl(panel: 'admin', parameters: $triageState);
    $query = crossEnvironmentCompareLaunchQuery($expectedUrl);
    $query['policy_type'] = ['deviceConfiguration'];

    $this->usePortfolioTriageWorkspace($user, $anchorTenant);

    $component = Livewire::withQueryParams($query)
        ->actingAs($user)
        ->test(CrossEnvironmentComparePage::class)
        ->assertSet('sourceEnvironmentId', (string) $anchorTenant->getKey())
        ->assertSet('targetEnvironmentId', (string) $targetEnvironment->getKey())
        ->assertSet('selectedPolicyTypes', ['deviceConfiguration'])
        ->assertActionVisible('return_to_origin')
        ->assertActionExists('return_to_origin', fn (Action $action): bool => $action->getLabel() === __('localization.shell.back_to_environment_registry')
            && $action->getUrl() === $expectedBackUrl);

    $page = $component->instance();
    $page->generatePromotionPreflight();
    $page->executePromotion();

    $run = OperationRun::query()->latest('id')->first();
    $navigationContext = CanonicalNavigationContext::fromPayload($page->navigationContextPayload);

    expect($run)
        ->not->toBeNull()
        ->and($run?->type)->toBe('promotion.execute')
        ->and(data_get($run?->context, 'selection.sourceEnvironmentId'))->toBe((int) $anchorTenant->getKey())
        ->and(data_get($run?->context, 'selection.targetEnvironmentId'))->toBe((int) $targetEnvironment->getKey())
        ->and(data_get($run?->context, 'selection.policyTypes'))->toBe(['deviceConfiguration'])
        ->and($page->sourceEnvironmentId)->toBe((string) $anchorTenant->getKey())
        ->and($page->targetEnvironmentId)->toBe((string) $targetEnvironment->getKey())
        ->and($page->selectedPolicyTypes)->toBe(['deviceConfiguration'])
        ->and($page->navigationContextPayload)->toBe($query['nav'])
        ->and($navigationContext?->backLinkLabel)->toBe(__('localization.shell.back_to_environment_registry'))
        ->and($navigationContext?->backLinkUrl)->toBe($expectedBackUrl);

    Queue::assertPushed(CrossEnvironmentPromotionExecutionJob::class, function (CrossEnvironmentPromotionExecutionJob $job) use ($run): bool {
        return $job->getOperationRun()?->is($run);
    });
});

it('rejects the bulk compare action until exactly two active environments are selected', function (): void {
    [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
    $targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');
    $thirdTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Third ManagedEnvironment');

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->selectTableRecords([$anchorTenant])
        ->assertTableBulkActionVisible('compareSelected')
        ->callTableBulkAction('compareSelected', [$anchorTenant])
        ->assertNotified('Select exactly two environments to compare.');

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->selectTableRecords([$anchorTenant, $targetEnvironment, $thirdTenant])
        ->assertTableBulkActionVisible('compareSelected')
        ->callTableBulkAction('compareSelected', [$anchorTenant, $targetEnvironment, $thirdTenant])
        ->assertNotified('Select exactly two environments to compare.');
});

it('rejects the bulk compare action when a selected environment is not active', function (): void {
    [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
    $onboardingTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Onboarding ManagedEnvironment');

    $onboardingTenant->forceFill([
        'status' => ManagedEnvironment::STATUS_ONBOARDING,
    ])->save();

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->selectTableRecords([$anchorTenant, $onboardingTenant])
        ->assertTableBulkActionVisible('compareSelected')
        ->callTableBulkAction('compareSelected', [$anchorTenant, $onboardingTenant])
        ->assertNotified('Only active environments can be compared.');
});

it('hides the compare launch action when workspace baseline view capability is missing', function (): void {
    [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
    $targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');

    $resolver = \Mockery::mock(WorkspaceCapabilityResolver::class);
    $resolver->shouldReceive('isMember')->andReturnTrue();
    $resolver->shouldReceive('can')->andReturnFalse();
    app()->instance(WorkspaceCapabilityResolver::class, $resolver);

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->assertTableActionHidden('compareEnvironments', $targetEnvironment);
});

it('hides the compare launch action when the actor lacks tenant view on the launched tenant', function (): void {
    [$user, $anchorTenant] = $this->makePortfolioTriageActor('Anchor ManagedEnvironment');
    $targetEnvironment = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target ManagedEnvironment');

    $resolver = \Mockery::mock(CapabilityResolver::class);
    $resolver->shouldReceive('primeMemberships')->andReturnNull();
    $resolver->shouldReceive('isMember')->andReturnTrue();
    $resolver->shouldReceive('can')->andReturnUsing(function (mixed $actor, mixed $tenant, string $capability) use ($targetEnvironment): bool {
        if ($tenant instanceof ManagedEnvironment
            && (int) $tenant->getKey() === (int) $targetEnvironment->getKey()
            && $capability === Capabilities::TENANT_VIEW) {
            return false;
        }

        return true;
    });
    app()->instance(CapabilityResolver::class, $resolver);

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->assertTableActionHidden('compareEnvironments', $targetEnvironment);
});
