<?php

declare(strict_types=1);

use App\Filament\Pages\EnvironmentDashboard;
use App\Filament\Resources\ManagedEnvironmentResource;
use App\Filament\Resources\ManagedEnvironmentResource\Pages\ListManagedEnvironments;
use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Support\BackupHealth\BackupFreshnessEvaluation;
use App\Support\BackupHealth\BackupScheduleFollowUpEvaluation;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\BackupHealth\TenantBackupHealthResolver;
use App\Support\RestoreSafety\RestoreSafetyResolver;
use App\Support\Workspaces\WorkspaceContext;
use Carbon\CarbonImmutable;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

use function Pest\Laravel\mock;

uses(RefreshDatabase::class);

afterEach(function (): void {
    CarbonImmutable::setTestNow();
});

function tenantRegistryBaseContext(string $anchorName = 'Anchor ManagedEnvironment'): array
{
    $tenant = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'name' => $anchorName,
    ]);

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

    return [$user, $tenant];
}

function tenantRegistryPeer(User $user, ManagedEnvironment $workspaceTenant, string $name): ManagedEnvironment
{
    $tenant = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'workspace_id' => (int) $workspaceTenant->workspace_id,
        'name' => $name,
    ]);

    createUserWithTenant(
        tenant: $tenant,
        user: $user,
        role: 'owner',
        workspaceRole: 'readonly',
    );

    return $tenant;
}

function tenantRegistryList(ManagedEnvironment $workspaceTenant, User $user, array $query = [])
{
    test()->actingAs($user);
    session()->put(WorkspaceContext::SESSION_KEY, (int) $workspaceTenant->workspace_id);
    Filament::setTenant(null, true);
    request()->attributes->remove('tenant_resource.posture_snapshot');
    session()->forget('tables.'.md5(ListManagedEnvironments::class).'_filters');
    session()->forget('tables.'.md5(ListManagedEnvironments::class).'_search');
    session()->forget('tables.'.md5(ListManagedEnvironments::class).'_sort');

    $factory = $query !== []
        ? Livewire::withQueryParams($query)->actingAs($user)
        : Livewire::actingAs($user);

    return $factory->test(ListManagedEnvironments::class);
}

function tenantRegistryBackupAssessment(
    int $tenantId,
    string $posture,
    ?string $reason = null,
    ?string $supportingMessage = null,
): TenantBackupHealthAssessment {
    return new TenantBackupHealthAssessment(
        tenantId: $tenantId,
        posture: $posture,
        primaryReason: $reason,
        headline: str($posture)->headline()->toString(),
        supportingMessage: $supportingMessage,
        latestRelevantBackupSetId: null,
        latestRelevantCompletedAt: now()->subMinutes(10),
        qualitySummary: null,
        freshnessEvaluation: new BackupFreshnessEvaluation(
            latestCompletedAt: now()->subMinutes(10),
            cutoffAt: now()->subHour(),
            isFresh: true,
        ),
        scheduleFollowUp: new BackupScheduleFollowUpEvaluation(
            hasEnabledSchedules: false,
            enabledScheduleCount: 0,
            overdueScheduleCount: 0,
            failedRecentRunCount: 0,
            neverSuccessfulCount: 0,
            needsFollowUp: false,
            primaryScheduleId: null,
            summaryMessage: null,
        ),
        healthyClaimAllowed: $posture === TenantBackupHealthAssessment::POSTURE_HEALTHY,
        primaryActionTarget: null,
        positiveClaimBoundary: 'Recent backup history does not prove tenant recovery.',
    );
}

function tenantRegistryRecoveryEvidence(
    string $overviewState,
    string $summary = 'Bounded recovery evidence summary.',
    string $reason = 'no_recent_issues_visible',
): array {
    return [
        'overview_state' => $overviewState,
        'summary' => $summary,
        'claim_boundary' => 'ManagedEnvironment-wide recovery is not proven.',
        'reason' => $reason,
        'latest_relevant_restore_run' => null,
        'latest_relevant_attention' => null,
        'latest_relevant_attention_state' => null,
    ];
}

it('shows separate backup posture and recovery evidence signals without turning metadata into recovery truth', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC'));

    [$user, $absentTenant] = tenantRegistryBaseContext('Absent Backup ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($absentTenant);

    $weakenedTenant = tenantRegistryPeer($user, $absentTenant, 'Weakened Recovery ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($weakenedTenant);
    $weakenedBackup = workspaceOverviewSeedHealthyBackup($weakenedTenant, [
        'completed_at' => now()->subMinutes(20),
    ]);
    workspaceOverviewSeedRestoreHistory($weakenedTenant, $weakenedBackup, 'follow_up');

    $metadataTenant = tenantRegistryPeer($user, $absentTenant, 'Metadata Drift ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($metadataTenant);
    $metadataBackup = workspaceOverviewSeedHealthyBackup($metadataTenant, [
        'completed_at' => now()->subMinutes(15),
    ]);
    workspaceOverviewSeedRestoreHistory($metadataTenant, $metadataBackup, 'completed');
    Policy::factory()->for($metadataTenant)->create([
        'display_name' => 'Stale sync policy',
        'last_synced_at' => now()->subDays(14),
    ]);

    $component = tenantRegistryList($absentTenant, $user)
        ->assertTableColumnExists('backup_posture')
        ->assertTableColumnExists('recovery_evidence')
        ->assertTableColumnVisible('backup_posture')
        ->assertTableColumnVisible('recovery_evidence')
        ->assertTableColumnFormattedStateSet('backup_posture', 'Absent', $absentTenant)
        ->assertTableColumnFormattedStateSet('recovery_evidence', 'Weakened', $weakenedTenant)
        ->assertTableColumnFormattedStateSet('backup_posture', 'Healthy', $metadataTenant)
        ->assertTableColumnFormattedStateSet('recovery_evidence', 'No recent issues visible', $metadataTenant)
        ->assertTableActionVisible('openTenant', $weakenedTenant)
        ->assertTableActionHasUrl('openTenant', EnvironmentDashboard::getUrl(panel: 'admin', tenant: $weakenedTenant), $weakenedTenant)
        ->assertDontSee('recoverable')
        ->assertDontSee('recovery proven')
        ->assertDontSee('validated overall');

    expect($component->instance()->getTable()->getRecordUrl($weakenedTenant))
        ->toBe(ManagedEnvironmentResource::getUrl('view', ['record' => $weakenedTenant], panel: 'admin'));
});

it('filters the registry to exact backup and recovery posture slices', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC'));

    [$user, $calmTenant] = tenantRegistryBaseContext('Calm ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($calmTenant);
    $calmBackup = workspaceOverviewSeedHealthyBackup($calmTenant, [
        'completed_at' => now()->subMinutes(10),
    ]);
    workspaceOverviewSeedRestoreHistory($calmTenant, $calmBackup, 'completed');

    $degradedTenant = tenantRegistryPeer($user, $calmTenant, 'Degraded Backup ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($degradedTenant);
    $degradedBackup = workspaceOverviewSeedHealthyBackup($degradedTenant, [
        'completed_at' => now()->subMinutes(12),
        'item_count' => 2,
    ], [
        'payload' => [],
        'metadata' => [
            'source' => 'metadata_only',
            'assignments_fetch_failed' => true,
        ],
        'assignments' => [],
    ]);
    workspaceOverviewSeedRestoreHistory($degradedTenant, $degradedBackup, 'completed');

    $unvalidatedTenant = tenantRegistryPeer($user, $calmTenant, 'Unvalidated Recovery ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($unvalidatedTenant);
    workspaceOverviewSeedHealthyBackup($unvalidatedTenant, [
        'completed_at' => now()->subMinutes(11),
    ]);

    tenantRegistryList($calmTenant, $user)
        ->assertTableColumnFormattedStateSet('recovery_evidence', 'Unvalidated', $unvalidatedTenant);

    $tenantResourceReflection = new ReflectionClass(ManagedEnvironmentResource::class);
    $postureSnapshot = $tenantResourceReflection->getMethod('postureSnapshot');
    $postureSnapshot->setAccessible(true);

    expect($postureSnapshot->invoke(null)['recovery_evidence_ids']['unvalidated'] ?? [])
        ->toBe([(int) $unvalidatedTenant->getKey()]);

    $backupFiltered = tenantRegistryList($calmTenant, $user)
        ->filterTable('backup_posture', [TenantBackupHealthAssessment::POSTURE_DEGRADED]);

    expect($backupFiltered->instance()->getFilteredTableQuery()?->pluck('managed_environments.name')->all())
        ->toBe(['Degraded Backup ManagedEnvironment']);

    $recoveryFiltered = tenantRegistryList($calmTenant, $user)
        ->filterTable('recovery_evidence', ['unvalidated'])
        ->assertSet('tableFilters.recovery_evidence.values', ['unvalidated']);

    expect($recoveryFiltered->instance()->getFilteredTableQuery()?->pluck('managed_environments.name')->all())
        ->toBe(['Unvalidated Recovery ManagedEnvironment'])
        ->and($recoveryFiltered->instance()->getTable()->getRecordUrl($unvalidatedTenant))
        ->toBe(ManagedEnvironmentResource::getUrl('view', ['record' => $unvalidatedTenant], panel: 'admin'));
});

it('orders the visible tenant registry worst-first with stable tenant-name tie breaks when triage sorting is requested', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC'));

    [$user, $absentTenant] = tenantRegistryBaseContext('Absent Backup ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($absentTenant);

    $alphaWeakenedTenant = tenantRegistryPeer($user, $absentTenant, 'Alpha Weakened ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($alphaWeakenedTenant);
    $alphaWeakenedBackup = workspaceOverviewSeedHealthyBackup($alphaWeakenedTenant, [
        'completed_at' => now()->subMinutes(20),
    ]);
    workspaceOverviewSeedRestoreHistory($alphaWeakenedTenant, $alphaWeakenedBackup, 'follow_up');

    $zetaWeakenedTenant = tenantRegistryPeer($user, $absentTenant, 'Zeta Weakened ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($zetaWeakenedTenant);
    $zetaWeakenedBackup = workspaceOverviewSeedHealthyBackup($zetaWeakenedTenant, [
        'completed_at' => now()->subMinutes(21),
    ]);
    workspaceOverviewSeedRestoreHistory($zetaWeakenedTenant, $zetaWeakenedBackup, 'failed');

    $staleTenant = tenantRegistryPeer($user, $absentTenant, 'Stale Backup ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($staleTenant);
    $staleBackup = workspaceOverviewSeedHealthyBackup($staleTenant, [
        'completed_at' => now()->subDays(2),
    ]);
    workspaceOverviewSeedRestoreHistory($staleTenant, $staleBackup, 'completed');

    $unvalidatedTenant = tenantRegistryPeer($user, $absentTenant, 'Unvalidated Recovery ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($unvalidatedTenant);
    workspaceOverviewSeedHealthyBackup($unvalidatedTenant, [
        'completed_at' => now()->subMinutes(18),
    ]);

    $degradedTenant = tenantRegistryPeer($user, $absentTenant, 'Degraded Backup ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($degradedTenant);
    $degradedBackup = workspaceOverviewSeedHealthyBackup($degradedTenant, [
        'completed_at' => now()->subMinutes(17),
        'item_count' => 2,
    ], [
        'payload' => [],
        'metadata' => [
            'source' => 'metadata_only',
            'assignments_fetch_failed' => true,
        ],
        'assignments' => [],
    ]);
    workspaceOverviewSeedRestoreHistory($degradedTenant, $degradedBackup, 'completed');

    $calmTenant = tenantRegistryPeer($user, $absentTenant, 'Calm ManagedEnvironment');
    workspaceOverviewSeedQuietTenantTruth($calmTenant);
    $calmBackup = workspaceOverviewSeedHealthyBackup($calmTenant, [
        'completed_at' => now()->subMinutes(14),
    ]);
    workspaceOverviewSeedRestoreHistory($calmTenant, $calmBackup, 'completed');

    tenantRegistryList($absentTenant, $user, [
        'triage_sort' => 'worst_first',
    ])
        ->assertSet('tableFilters.triage_sort.value', 'worst_first')
        ->assertCanSeeTableRecords([
            $absentTenant,
            $alphaWeakenedTenant,
            $zetaWeakenedTenant,
            $staleTenant,
            $unvalidatedTenant,
            $degradedTenant,
            $calmTenant,
        ], inOrder: true);
});

it('loads backup posture and recovery evidence with one batch per registry render instead of per-row fanout', function (): void {
    [$user, $firstTenant] = tenantRegistryBaseContext('Batch ManagedEnvironment Alpha');
    $secondTenant = tenantRegistryPeer($user, $firstTenant, 'Batch ManagedEnvironment Beta');

    $backupAssessments = [
        (int) $firstTenant->getKey() => tenantRegistryBackupAssessment(
            tenantId: (int) $firstTenant->getKey(),
            posture: TenantBackupHealthAssessment::POSTURE_STALE,
            reason: TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE,
            supportingMessage: 'The latest backup is outside the configured freshness window.',
        ),
        (int) $secondTenant->getKey() => tenantRegistryBackupAssessment(
            tenantId: (int) $secondTenant->getKey(),
            posture: TenantBackupHealthAssessment::POSTURE_HEALTHY,
        ),
    ];

    $expectedTenantIds = [
        (int) $firstTenant->getKey(),
        (int) $secondTenant->getKey(),
    ];

    $backupResolver = new class($expectedTenantIds, $backupAssessments)
    {
        public int $assessManyCalls = 0;

        /**
         * @param  list<int>  $expectedTenantIds
         * @param  array<int, TenantBackupHealthAssessment>  $assessments
         */
        public function __construct(
            private array $expectedTenantIds,
            private array $assessments,
        ) {}

        /**
         * @return array<int, TenantBackupHealthAssessment>
         */
        public function assessMany(iterable $tenantIds): array
        {
            $this->assessManyCalls++;

            expect(array_values(is_array($tenantIds) ? $tenantIds : iterator_to_array($tenantIds, false)))
                ->toBe($this->expectedTenantIds);

            return $this->assessments;
        }
    };

    $restoreSafetyResolver = new class($expectedTenantIds, $backupAssessments)
    {
        public int $dashboardEvidenceCalls = 0;

        /**
         * @param  list<int>  $expectedTenantIds
         * @param  array<int, TenantBackupHealthAssessment>  $expectedAssessments
         */
        public function __construct(
            private array $expectedTenantIds,
            private array $expectedAssessments,
        ) {}

        /**
         * @param  array<int, TenantBackupHealthAssessment>  $resolvedAssessments
         * @return array<int, array<string, mixed>>
         */
        public function dashboardRecoveryEvidenceForTenants(array $tenantIds, array $resolvedAssessments): array
        {
            $this->dashboardEvidenceCalls++;

            expect($tenantIds)->toBe($this->expectedTenantIds)
                ->and($resolvedAssessments)->toBe($this->expectedAssessments);

            return [
                $tenantIds[0] => tenantRegistryRecoveryEvidence(
                    overviewState: 'unvalidated',
                    summary: 'No recent restore history is available for this tenant.',
                    reason: 'no_history',
                ),
                $tenantIds[1] => tenantRegistryRecoveryEvidence(
                    overviewState: 'no_recent_issues_visible',
                ),
            ];
        }
    };

    app()->instance(TenantBackupHealthResolver::class, $backupResolver);
    app()->instance(RestoreSafetyResolver::class, $restoreSafetyResolver);

    tenantRegistryList($firstTenant, $user)
        ->assertCanSeeTableRecords([$firstTenant, $secondTenant])
        ->assertTableColumnFormattedStateSet('backup_posture', 'Stale', $firstTenant)
        ->assertTableColumnFormattedStateSet('recovery_evidence', 'No recent issues visible', $secondTenant);

    expect($backupResolver->assessManyCalls)->toBe(1)
        ->and($restoreSafetyResolver->dashboardEvidenceCalls)->toBe(1);
});
