<?php

declare(strict_types=1);

use App\Filament\Pages\CrossTenantComparePage;
use App\Filament\Resources\TenantResource;
use App\Jobs\Operations\CrossTenantPromotionExecutionJob;
use App\Models\OperationRun;
use App\Models\Tenant;
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 crossTenantCompareLaunchQuery(string $url): array
{
    parse_str((string) parse_url($url, PHP_URL_QUERY), $query);

    return $query;
}

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

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

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

    $expectedUrl = TenantResource::crossTenantCompareOpenUrl($targetTenant, $triageState);

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

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

    expect($query)->toMatchArray([
        'target_tenant_id' => (string) $targetTenant->getKey(),
    ])
        ->and(data_get($query, 'nav.source_surface'))->toBe('tenant_registry')
        ->and(data_get($query, 'nav.tenant_id'))->toBe((string) $targetTenant->getKey())
        ->and(data_get($query, 'nav.back_label'))->toBe('Back to tenant 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(CrossTenantComparePage::class)
        ->assertSet('sourceTenantId', null)
        ->assertSet('targetTenantId', (string) $targetTenant->getKey())
        ->assertActionVisible('return_to_origin')
        ->assertActionExists('return_to_origin', fn (Action $action): bool => $action->getLabel() === 'Back to tenant registry'
            && $action->getUrl() === TenantResource::getUrl(panel: 'admin', parameters: $triageState));
});

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

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

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

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

    $expectedUrl = TenantResource::crossTenantCompareOpenUrlForSelection(
        targetTenant: $targetTenant,
        triageState: $triageState,
        sourceTenant: $anchorTenant,
    );

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

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

    expect($query)->toMatchArray([
        'source_tenant_id' => (string) $anchorTenant->getKey(),
        'target_tenant_id' => (string) $targetTenant->getKey(),
    ])
        ->and(data_get($query, 'nav.source_surface'))->toBe('tenant_registry')
        ->and(data_get($query, 'nav.back_label'))->toBe('Back to tenant registry')
        ->and(data_get($query, 'nav.tenant_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(CrossTenantComparePage::class)
        ->assertSet('sourceTenantId', (string) $anchorTenant->getKey())
        ->assertSet('targetTenantId', (string) $targetTenant->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 Tenant',
        workspaceRole: 'owner',
    );
    $targetTenant = $this->makePortfolioTriagePeer($user, $anchorTenant, 'Target Tenant');
    createMinimalUserWithTenant(
        tenant: $targetTenant,
        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 = TenantResource::crossTenantCompareOpenUrlForSelection(
        targetTenant: $targetTenant,
        triageState: $triageState,
        sourceTenant: $anchorTenant,
    );
    $expectedBackUrl = TenantResource::getUrl(panel: 'admin', parameters: $triageState);
    $query = crossTenantCompareLaunchQuery($expectedUrl);
    $query['policy_type'] = ['deviceConfiguration'];

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

    $component = Livewire::withQueryParams($query)
        ->actingAs($user)
        ->test(CrossTenantComparePage::class)
        ->assertSet('sourceTenantId', (string) $anchorTenant->getKey())
        ->assertSet('targetTenantId', (string) $targetTenant->getKey())
        ->assertSet('selectedPolicyTypes', ['deviceConfiguration'])
        ->assertActionVisible('return_to_origin')
        ->assertActionExists('return_to_origin', fn (Action $action): bool => $action->getLabel() === 'Back to tenant 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.sourceTenantId'))->toBe((int) $anchorTenant->getKey())
        ->and(data_get($run?->context, 'selection.targetTenantId'))->toBe((int) $targetTenant->getKey())
        ->and(data_get($run?->context, 'selection.policyTypes'))->toBe(['deviceConfiguration'])
        ->and($page->sourceTenantId)->toBe((string) $anchorTenant->getKey())
        ->and($page->targetTenantId)->toBe((string) $targetTenant->getKey())
        ->and($page->selectedPolicyTypes)->toBe(['deviceConfiguration'])
        ->and($page->navigationContextPayload)->toBe($query['nav'])
        ->and($navigationContext?->backLinkLabel)->toBe('Back to tenant registry')
        ->and($navigationContext?->backLinkUrl)->toBe($expectedBackUrl);

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

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

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

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

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

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

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

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

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

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->assertTableActionHidden('compareTenants', $targetTenant);
});

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

    $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 ($targetTenant): bool {
        if ($tenant instanceof Tenant
            && (int) $tenant->getKey() === (int) $targetTenant->getKey()
            && $capability === Capabilities::TENANT_VIEW) {
            return false;
        }

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

    $this->portfolioTriageRegistryList($user, $anchorTenant)
        ->assertTableActionHidden('compareTenants', $targetTenant);
});