<?php

declare(strict_types=1);

use App\Filament\Pages\EnvironmentDashboard;
use App\Filament\Resources\FindingExceptionResource;
use App\Filament\Resources\FindingResource;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Support\EnvironmentDashboard\EnvironmentDashboardSummaryBuilder;
use App\Support\Links\RequiredPermissionsLinks;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;

use function Pest\Laravel\mock;

function mockEnvironmentDashboardActionPermissions(array $overview = []): void
{
    mock(ManagedEnvironmentRequiredPermissionsViewModelBuilder::class, function ($mock) use ($overview): void {
        $mock->shouldReceive('build')->andReturn([
            'overview' => array_replace_recursive([
                'overall' => 'ready',
                'counts' => [
                    'missing_application' => 0,
                    'missing_delegated' => 0,
                ],
                'freshness' => [
                    'is_stale' => false,
                    'last_refreshed_at' => now()->toIso8601String(),
                ],
            ], $overview),
        ]);
    });
}

/**
 * @return list<string>
 */
function tenantDashboardButtonClassesForXPath(string $content, string $xpathExpression): array
{
    $dom = new \DOMDocument;

    libxml_use_internal_errors(true);
    $dom->loadHTML($content);
    libxml_clear_errors();

    $xpath = new \DOMXPath($dom);
    $nodes = $xpath->query($xpathExpression);

    if ($nodes === false) {
        return [];
    }

    return collect(iterator_to_array($nodes))
        ->map(static fn (\DOMNode $node): string => (string) $node->attributes?->getNamedItem('class')?->nodeValue)
        ->filter()
        ->values()
        ->all();
}

it('builds the canonical operations follow-up baseline with tenant continuity', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    [, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');

    expect(OperationRunLinks::index($tenant, activeTab: 'active'))
        ->toBe(route('admin.operations.index', [
            'workspace' => $tenant->workspace,
            'environment_id' => (int) $tenant->getKey(),
            'activeTab' => 'active',
        ]))
        ->and(OperationRunLinks::index(
            $tenant,
            activeTab: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
            problemClass: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
        ))
        ->toBe(route('admin.operations.index', [
            'workspace' => $tenant->workspace,
            'environment_id' => (int) $tenant->getKey(),
            'activeTab' => OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
            'problemClass' => OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
        ]));
});

it('builds the required-permissions follow-up baseline with tenant continuity', function (): void {
    $tenant = ManagedEnvironment::factory()->create([
        'external_id' => 'tenant-dashboard-productization',
    ]);
    [, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');

    expect(RequiredPermissionsLinks::requiredPermissions($tenant, ['source' => 'tenant_dashboard']))
        ->toBe(url('/admin/workspaces/'.urlencode((string) $tenant->workspace->slug).'/environments/'.urlencode((string) $tenant->getRouteKey()).'/required-permissions?source=tenant_dashboard'));
});

it('prioritizes operations requiring attention below permissions and high severity findings and keeps canonical hub links', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    mockEnvironmentDashboardActionPermissions([
        'overall' => 'blocked',
        'counts' => [
            'missing_application' => 1,
            'missing_delegated' => 0,
        ],
    ]);

    Finding::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_NEW,
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'inventory_sync',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Failed->value,
        'created_at' => now()->subMinute(),
        'started_at' => now()->subMinutes(2),
        'completed_at' => now()->subMinute(),
    ]);

    $summary = app(EnvironmentDashboardSummaryBuilder::class)
        ->build($tenant, $user)
        ->toArray();

    $activeOperationSummary = $summary['activeOperationSummary'] ?? null;
    $recommendedActions = $summary['recommendedActions'] ?? [];

    expect($activeOperationSummary)
        ->not->toBeNull()
        ->and($activeOperationSummary['items'][0]['primaryActionLabel'] ?? null)->toBe('Review operation')
        ->and($activeOperationSummary['items'][0]['primaryActionUrl'] ?? null)->toBe(OperationRunLinks::view($run, $tenant))
        ->and($activeOperationSummary['secondaryActionLabel'] ?? null)->toBe('Open operations hub')
        ->and($activeOperationSummary['secondaryActionUrl'] ?? null)->toBe(OperationRunLinks::index($tenant, activeTab: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP, problemClass: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP))
        ->and(array_column($recommendedActions, 'key'))->toBe([
            'required_permissions',
            'high_severity_findings',
            'operations_requiring_attention',
        ])
        ->and($recommendedActions[2]['title'] ?? null)->toBe('Review operations requiring attention')
        ->and($recommendedActions[2]['reason'] ?? null)->toBe('One or more operations finished with an outcome that needs follow-up.')
        ->and($recommendedActions[2]['impact'] ?? null)->toBe('The environment should not be treated as fully healthy until the operation outcome has been reviewed.')
        ->and($recommendedActions[2]['actionLabel'] ?? null)->toBe('Review operations')
        ->and($recommendedActions[2]['actionUrl'] ?? null)->toBe(OperationRunLinks::index($tenant, activeTab: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP, problemClass: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP));
});

it('uses review permissions as the top recommended-action CTA when permissions are the highest follow-up', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    mockEnvironmentDashboardActionPermissions([
        'overall' => 'blocked',
        'counts' => [
            'missing_application' => 2,
            'missing_delegated' => 0,
        ],
    ]);

    $recommendedActions = app(EnvironmentDashboardSummaryBuilder::class)
        ->build($tenant, $user)
        ->toArray()['recommendedActions'];

    expect($recommendedActions[0]['key'] ?? null)->toBe('required_permissions')
        ->and($recommendedActions[0]['title'] ?? null)->toBe('Review permissions')
        ->and($recommendedActions[0]['actionLabel'] ?? null)->toBe('Review permissions')
        ->and($recommendedActions[0]['actionUrl'] ?? null)->toBe(RequiredPermissionsLinks::requiredPermissions($tenant));
});

it('orders productized recommended actions by priority and caps the visible list at three repo-real CTAs', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    mockEnvironmentDashboardActionPermissions([
        'overall' => 'blocked',
        'counts' => [
            'missing_application' => 2,
            'missing_delegated' => 0,
        ],
    ]);

    [$profile, $snapshot] = seedActiveBaselineForTenant($tenant);

    seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage());

    $backupSet = workspaceOverviewSeedHealthyBackup($tenant);

    workspaceOverviewSeedRestoreHistory($tenant, $backupSet);

    Finding::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_NEW,
    ]);

    $riskFinding = Finding::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'severity' => Finding::SEVERITY_LOW,
        'status' => Finding::STATUS_RISK_ACCEPTED,
    ]);

    FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $riskFinding->getKey(),
        'requested_by_user_id' => (int) $user->getKey(),
        'owner_user_id' => (int) $user->getKey(),
        'approved_by_user_id' => (int) $user->getKey(),
        'status' => FindingException::STATUS_ACTIVE,
        'current_validity_state' => FindingException::VALIDITY_EXPIRED,
        'request_reason' => 'Expired risk acceptance for productization ordering',
        'approval_reason' => 'Approved for regression',
        'requested_at' => now()->subDays(7),
        'approved_at' => now()->subDays(6),
        'effective_from' => now()->subDays(6),
        'review_due_at' => now()->subDay(),
        'expires_at' => now()->subDay(),
        'evidence_summary' => ['reference_count' => 0],
    ]);

    $summary = app(EnvironmentDashboardSummaryBuilder::class)
        ->build($tenant, $user)
        ->toArray();

    $actions = $summary['recommendedActions'];

    expect(array_column($actions, 'key'))
        ->toBe(['required_permissions', 'high_severity_findings', 'risk_exceptions'])
        ->and(count($actions))->toBe(3)
        ->and(array_column($actions, 'icon'))->toBe([
            'heroicon-m-shield-exclamation',
            'heroicon-m-shield-exclamation',
            'heroicon-o-exclamation-triangle',
        ])
        ->and($actions[0]['actionUrl'])->toBe(RequiredPermissionsLinks::requiredPermissions($tenant))
        ->and($actions[1]['actionUrl'])->toBe(FindingResource::getUrl('index', [
            'tab' => 'needs_action',
            'high_severity' => 1,
        ], panel: 'admin', tenant: $tenant))
        ->and($actions[2]['actionUrl'])->toBe(FindingExceptionResource::getUrl('index', panel: 'admin', tenant: $tenant));

    $this->actingAs($user);
    setAdminEnvironmentContext($tenant);

    $content = $this->get(EnvironmentDashboard::getUrl(panel: 'admin', tenant: $tenant))
        ->assertSuccessful()
        ->getContent();

    $recommendedButtonClasses = tenantDashboardButtonClassesForXPath(
        $content,
        "//*[@data-testid='tenant-dashboard-recommended-action']//*[self::a or self::button][contains(@class, 'fi-btn')]",
    );
    $asideButtonClasses = tenantDashboardButtonClassesForXPath(
        $content,
        "//*[@data-testid='tenant-dashboard-readiness-card']//*[self::a or self::button][contains(@class, 'fi-btn')]",
    );
    $priorityMarkerClasses = tenantDashboardButtonClassesForXPath(
        $content,
        "//*[@data-testid='tenant-dashboard-recommended-action-priority']",
    );

    expect(substr_count($content, 'data-testid="tenant-dashboard-recommended-action"'))->toBe(3)
        ->and(substr_count($content, 'data-testid="tenant-dashboard-recommended-action-icon"'))->toBe(3)
        ->and($content)->toContain('data-icon="heroicon-m-shield-exclamation"')
        ->and($content)->toContain('data-icon="heroicon-o-exclamation-triangle"')
        ->and($recommendedButtonClasses)->not->toBeEmpty()
        ->and($asideButtonClasses)->not->toBeEmpty()
        ->and(collect([...$recommendedButtonClasses, ...$asideButtonClasses])->contains(static fn (string $classes): bool => str_contains($classes, 'fi-outlined')))->toBeFalse()
        ->and(collect($priorityMarkerClasses)->every(static fn (string $classes): bool => str_contains($classes, 'border-gray-200')
            && str_contains($classes, 'bg-gray-50')
            && str_contains($classes, 'text-gray-700')))->toBeTrue();
});

it('assigns semantically distinct icons to overdue-findings and recovery-posture follow-ups', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    mockEnvironmentDashboardActionPermissions();

    [$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
    seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage());

    $backupSet = workspaceOverviewSeedHealthyBackup($tenant);
    workspaceOverviewSeedRestoreHistory($tenant, $backupSet, 'follow_up');

    Finding::factory()
        ->for($tenant)
        ->overdueByHours()
        ->create([
            'workspace_id' => (int) $tenant->workspace_id,
            'severity' => Finding::SEVERITY_LOW,
            'status' => Finding::STATUS_NEW,
        ]);

    $actions = app(EnvironmentDashboardSummaryBuilder::class)
        ->build($tenant, $user)
        ->toArray()['recommendedActions'];

    expect(collect($actions)->firstWhere('key', 'overdue_findings')['icon'] ?? null)
        ->toBe('heroicon-o-clock')
        ->and(collect($actions)->firstWhere('key', 'recovery_posture')['icon'] ?? null)
        ->toBe('heroicon-o-arrow-path-rounded-square');
});

it('keeps continue-review follow-up unavailable for readonly members who can only inspect review state', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'readonly');

    composeEnvironmentReviewForTest($tenant, $user);

    $summary = app(EnvironmentDashboardSummaryBuilder::class)
        ->build($tenant, $user)
        ->toArray();

    $continueReview = collect($summary['recommendedActions'])->firstWhere('key', 'continue_review');

    expect($continueReview)
        ->not->toBeNull()
        ->and($continueReview['actionDisabled'])->toBeTrue()
        ->and($continueReview['actionUrl'])->toBeNull()
        ->and($continueReview['helperText'])->toContain('continue the review');
});
