<?php

declare(strict_types=1);

use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Support\Inventory\InventoryCoverage as InventoryCoveragePayload;
use App\Support\Operations\ExecutionDenialReasonCode;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\ReasonTranslation\ReasonPresenter;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Testing\TestResponse;
use Tests\Feature\Baselines\Support\BaselineSubjectResolutionFixtures;

function visiblePageText(TestResponse $response): string
{
    $html = (string) $response->getContent();
    $html = preg_replace('/<script\b[^>]*>.*?<\/script>/is', '', $html) ?? $html;
    $html = preg_replace('/<style\b[^>]*>.*?<\/style>/is', '', $html) ?? $html;
    $html = preg_replace('/\s+wire:snapshot="[^"]*"/', '', $html) ?? $html;
    $html = preg_replace('/\s+wire:effects="[^"]*"/', '', $html) ?? $html;

    return trim((string) preg_replace('/\s+/', ' ', strip_tags($html)));
}

/**
 * @param  array<string, mixed>  $overrides
 * @return array<string, mixed>
 */
function baselineCompareGapContext(array $overrides = []): array
{
    return array_replace_recursive(BaselineSubjectResolutionFixtures::compareContext([
        BaselineSubjectResolutionFixtures::structuredGap([
            'policy_type' => 'deviceConfiguration',
            'subject_key' => 'WiFi-Corp-Profile',
            'resolution_outcome' => 'ambiguous_match',
            'reason_code' => 'ambiguous_match',
            'operator_action_category' => 'inspect_subject_mapping',
        ]),
        BaselineSubjectResolutionFixtures::structuredGap([
            'policy_type' => 'deviceConfiguration',
            'subject_key' => 'VPN-Always-On',
            'resolution_outcome' => 'ambiguous_match',
            'reason_code' => 'ambiguous_match',
            'operator_action_category' => 'inspect_subject_mapping',
        ]),
        BaselineSubjectResolutionFixtures::structuredGap([
            'policy_type' => 'deviceConfiguration',
            'subject_key' => 'Email-Exchange-Config',
            'resolution_outcome' => 'ambiguous_match',
            'reason_code' => 'ambiguous_match',
            'operator_action_category' => 'inspect_subject_mapping',
        ]),
        BaselineSubjectResolutionFixtures::structuredGap([
            'policy_type' => 'deviceConfiguration',
            'subject_key' => 'Deleted-Policy-ABC',
            'resolution_outcome' => 'policy_record_missing',
            'reason_code' => 'policy_record_missing',
            'operator_action_category' => 'run_policy_sync_or_backup',
        ]),
        BaselineSubjectResolutionFixtures::structuredGap([
            'policy_type' => 'deviceConfiguration',
            'subject_key' => 'Removed-Config-XYZ',
            'resolution_outcome' => 'policy_record_missing',
            'reason_code' => 'policy_record_missing',
            'operator_action_category' => 'run_policy_sync_or_backup',
        ]),
    ]), [
        'baseline_compare' => [
            'subjects_total' => 50,
            'reason_code' => 'evidence_capture_incomplete',
            'fidelity' => 'meta',
            'coverage' => [
                'proof' => true,
                'covered_types' => ['deviceConfiguration'],
                'uncovered_types' => [],
                'effective_types' => ['deviceConfiguration'],
            ],
            'evidence_capture' => [
                'requested' => 50,
                'succeeded' => 47,
                'skipped' => 0,
                'failed' => 3,
                'throttled' => 0,
            ],
        ],
    ], $overrides);
}

it('renders decision-first hierarchy before main sections and technical diagnostics', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    setAdminPanelContext();

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'policy.sync',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Succeeded->value,
        'initiator_name' => 'Alice Example',
        'summary_counts' => [
            'total' => 10,
            'processed' => 10,
            'succeeded' => 10,
        ],
        'context' => [
            'target_scope' => [
                'entra_tenant_name' => 'Contoso',
                'entra_tenant_id' => '11111111-1111-1111-1111-111111111111',
            ],
        ],
    ]);

    $response = $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Decision')
        ->assertSee('Timing')
        ->assertSee('Metadata')
        ->assertSee('Count diagnostics')
        ->assertSee('Contoso');

    $pageText = visiblePageText($response);

    $policySyncPosition = mb_strpos($pageText, 'Policy sync');
    $decisionPosition = mb_strpos($pageText, 'Decision');
    $timingPosition = mb_strpos($pageText, 'Timing');
    $metadataPosition = mb_strpos($pageText, 'Metadata');
    $relatedContextPosition = mb_strpos($pageText, 'Related context');
    $countDiagnosticsPosition = mb_strpos($pageText, 'Count diagnostics');
    $identityHashPosition = mb_strpos($pageText, 'Identity hash');

    expect($policySyncPosition)->not->toBeFalse()
        ->and($decisionPosition)->not->toBeFalse()
        ->and($timingPosition)->not->toBeFalse()
        ->and($metadataPosition)->not->toBeFalse()
        ->and($relatedContextPosition)->not->toBeFalse()
        ->and($countDiagnosticsPosition)->not->toBeFalse()
        ->and($identityHashPosition)->not->toBeFalse()
        ->and($policySyncPosition)->toBeLessThan($decisionPosition)
        ->and($decisionPosition)->toBeLessThan($timingPosition)
        ->and($timingPosition)->toBeLessThan($metadataPosition)
        ->and($metadataPosition)->toBeLessThan($relatedContextPosition)
        ->and($relatedContextPosition)->toBeLessThan($countDiagnosticsPosition)
        ->and($countDiagnosticsPosition)->toBeLessThan($identityHashPosition);

    expect((string) $response->getContent())
        ->toMatch('/fi-section-header-heading[^>]*>\s*Decision\s*</')
        ->toMatch('/fi-section-header-heading[^>]*>\s*Timing\s*</');

    $response->assertDontSee('Current state')
        ->assertDontSee('Run summary');
});

it('keeps header navigation and related context visible for tenant-bound operation runs', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);

    $backupSet = BackupSet::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'name' => 'Nightly backup',
    ]);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'backup_set.update',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Succeeded->value,
        'context' => [
            'backup_set_id' => (int) $backupSet->getKey(),
            'target_scope' => [
                'entra_tenant_name' => 'Contoso',
            ],
        ],
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Back to Operations')
        ->assertSee('Refresh')
        ->assertSee('Related context')
        ->assertSee(\App\Filament\Resources\BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant), false);
});

it('renders mismatch context above the enterprise detail content without blocking the page', function (): void {
    $runTenant = ManagedEnvironment::factory()->create([
        'name' => 'Run ManagedEnvironment',
    ]);
    [$user, $runTenant] = createUserWithTenant(tenant: $runTenant, role: 'owner');

    $currentTenant = ManagedEnvironment::factory()->create([
        'name' => 'Current ManagedEnvironment',
        'workspace_id' => (int) $runTenant->workspace_id,
    ]);

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

    setAdminPanelContext($currentTenant);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $runTenant->workspace_id,
        'managed_environment_id' => (int) $runTenant->getKey(),
        'type' => 'policy.sync',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Succeeded->value,
    ]);

    $response = $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $runTenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Current environment context differs from this operation')
        ->assertSee('Decision')
        ->assertSee('Related context');

    $pageText = visiblePageText($response);

    $bannerPosition = mb_strpos($pageText, 'Current environment context differs from this operation');
    $decisionPosition = mb_strpos($pageText, 'Decision');

    expect($bannerPosition)->not->toBeFalse()
        ->and($decisionPosition)->not->toBeFalse()
        ->and($bannerPosition)->toBeLessThan($decisionPosition);
});

it('renders explicit sparse-data fallbacks for operation runs', function (): void {
    $workspace = Workspace::factory()->create();
    $user = User::factory()->create();

    WorkspaceMembership::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'user_id' => (int) $user->getKey(),
        'role' => 'owner',
    ]);

    setAdminPanelContext();

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'managed_environment_id' => null,
        'type' => 'provider.connection.check',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Failed->value,
        'summary_counts' => [],
        'failure_summary' => [],
        'context' => [],
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('No target scope details were recorded for this operation.')
        ->assertSee('Verification report')
        ->assertSee('Verification report unavailable')
        ->assertDontSee('Counts');
});

it('renders lifecycle reconciliation diagnostics for reconciled runs', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'restore.execute',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Failed->value,
        'context' => [
            'reason_code' => 'run.adapter_out_of_sync',
            'reconciliation' => [
                'reconciled_at' => now()->toIso8601String(),
                'reason' => 'run.adapter_out_of_sync',
                'reason_code' => 'run.adapter_out_of_sync',
                'source' => 'adapter_reconciler',
            ],
        ],
        'failure_summary' => [[
            'code' => 'run.adapter_out_of_sync',
            'reason_code' => 'run.adapter_out_of_sync',
            'message' => 'A related restore record reached terminal truth before the operation run was updated.',
        ]],
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Lifecycle reconciliation')
        ->assertSee('Automatically reconciled')
        ->assertSee('Reconciled by')
        ->assertSee('Adapter reconciler');
});

it('renders explicit reason-owner and platform-family semantics for blocked runs', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'inventory_sync',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Blocked->value,
        'context' => [
            'reason_code' => ExecutionDenialReasonCode::MissingCapability->value,
            'execution_legitimacy' => [
                'reason_code' => ExecutionDenialReasonCode::MissingCapability->value,
            ],
        ],
        'failure_summary' => [[
            'code' => 'operation.blocked',
            'reason_code' => ExecutionDenialReasonCode::MissingCapability->value,
            'message' => 'Operation blocked because the initiating actor no longer has the required capability.',
        ]],
    ]);

    $reasonSemantics = app(ReasonPresenter::class)->semantics(
        app(ReasonPresenter::class)->forOperationRun($run, 'run_detail'),
    );

    expect($reasonSemantics)->not->toBeNull();

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Explanation semantics')
        ->assertSee('Reason owner')
        ->assertSee($reasonSemantics['owner_label'])
        ->assertSee('Platform reason family')
        ->assertSee($reasonSemantics['family_label']);
});

it('renders evidence gap details section for baseline compare runs with gap subjects', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'partially_succeeded',
        'summary_counts' => [
            'total' => 50,
            'processed' => 47,
            'failed' => 3,
        ],
        'context' => baselineCompareGapContext(),
        'completed_at' => now(),
    ]);

    $response = $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Decision')
        ->assertSee('Primary next step')
        ->assertSee('Count diagnostics')
        ->assertSee('Evidence gap details')
        ->assertSee('Search gap details')
        ->assertSee('Search by reason, type, class, outcome, action, or subject key')
        ->assertSee('Reason')
        ->assertSee('Ambiguous inventory match')
        ->assertSee('Policy record missing')
        ->assertSee('3 affected')
        ->assertSee('2 affected')
        ->assertSee('WiFi-Corp-Profile')
        ->assertSee('Deleted-Policy-ABC')
        ->assertSee('Governed subject')
        ->assertSee('Subject class')
        ->assertSee('Outcome')
        ->assertSee('Next action')
        ->assertSee('Subject key');

    $pageText = visiblePageText($response);

    $decisionPosition = mb_strpos($pageText, 'Decision');
    $timingPosition = mb_strpos($pageText, 'Timing');
    $searchGapDetailsPosition = mb_strpos($pageText, 'Search gap details');
    $gapDetailsPosition = mb_strpos($pageText, 'Evidence gap details');
    $countDiagnosticsPosition = mb_strpos($pageText, 'Count diagnostics');

    expect($decisionPosition)->not->toBeFalse()
        ->and($timingPosition)->not->toBeFalse()
        ->and($searchGapDetailsPosition)->not->toBeFalse()
        ->and($gapDetailsPosition)->not->toBeFalse()
        ->and($countDiagnosticsPosition)->not->toBeFalse()
        ->and($decisionPosition)->toBeLessThan($timingPosition)
        ->and($timingPosition)->toBeLessThan($gapDetailsPosition)
        ->and($gapDetailsPosition)->toBeLessThan($searchGapDetailsPosition)
        ->and($gapDetailsPosition)->toBeLessThan($countDiagnosticsPosition);
});

it('renders baseline compare evidence-gap details without invoking graph during canonical run detail render', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);
    bindFailHardGraphClient();

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'partially_succeeded',
        'context' => baselineCompareGapContext(),
        'completed_at' => now(),
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Evidence gap details')
        ->assertSee('WiFi-Corp-Profile');
});

it('distinguishes missing recorded gap detail from no-gap runs on the canonical run detail surface', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);

    $legacyRun = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'partially_succeeded',
        'context' => baselineCompareGapContext([
            'baseline_compare' => [
                'evidence_gaps' => [
                    'subjects' => null,
                ],
            ],
        ]),
        'completed_at' => now(),
    ]);

    $cleanRun = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'succeeded',
        'context' => baselineCompareGapContext([
            'baseline_compare' => [
                'reason_code' => 'no_drift_detected',
                'evidence_gaps' => [
                    'count' => 0,
                    'by_reason' => [],
                    'subjects' => [],
                ],
            ],
        ]),
        'completed_at' => now(),
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($legacyRun))
        ->assertOk()
        ->assertSee('Evidence gap details')
        ->assertSee('Detailed rows were not recorded for this run')
        ->assertSee('Baseline compare evidence');

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($cleanRun))
        ->assertOk()
        ->assertDontSee('Evidence gap details')
        ->assertSee('Baseline compare evidence');
});

it('returns 404 for workspace members without tenant entitlement when evidence-gap details exist on the canonical surface', function (): void {
    $workspace = Workspace::factory()->create();
    $tenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
    ]);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'partially_succeeded',
        'context' => baselineCompareGapContext(),
        'completed_at' => now(),
    ]);

    $user = User::factory()->create();

    WorkspaceMembership::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'user_id' => (int) $user->getKey(),
        'role' => 'owner',
    ]);

    $otherTenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
    ]);

    createUserWithTenant(tenant: $otherTenant, user: $user, role: 'owner');
    app(\App\Services\Auth\ManagedEnvironmentAccessScopeResolver::class)->clearCache();

    expect(app(\App\Services\Auth\ManagedEnvironmentAccessScopeResolver::class)->canAccess($user, $tenant))->toBeFalse();

    setAdminPanelContext();

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertNotFound();
});

it('renders a human-readable inventory sync coverage section before technical context', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    Filament::setTenant(null, true);

    $run = OperationRun::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => 'inventory_sync',
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::PartiallySucceeded->value,
        'context' => [
            'inventory' => [
                'coverage' => InventoryCoveragePayload::buildPayload([
                    'conditionalAccessPolicy' => [
                        'status' => InventoryCoveragePayload::StatusSucceeded,
                        'item_count' => 1,
                    ],
                    'deviceConfiguration' => [
                        'status' => InventoryCoveragePayload::StatusFailed,
                        'item_count' => 0,
                        'error_code' => 'graph_forbidden',
                    ],
                    'roleScopeTag' => [
                        'status' => InventoryCoveragePayload::StatusSkipped,
                        'item_count' => 0,
                    ],
                ], ['roleScopeTag']),
            ],
        ],
        'completed_at' => now(),
    ]);

    $response = $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee('Inventory sync coverage')
        ->assertSee('Execution outcome stays separate from the per-type results below.')
        ->assertSee('Coverage truth below explains which types created the follow-up.')
        ->assertSee('deviceConfiguration')
        ->assertSee('roleScopeTag')
        ->assertSee('Review provider consent or permissions, then rerun inventory sync.')
        ->assertSee('Run inventory sync again with the required types selected.');

    $pageText = visiblePageText($response);

    $coveragePosition = mb_strpos($pageText, 'Inventory sync coverage');
    $contextPosition = mb_strpos($pageText, 'Context');

    expect($coveragePosition)->not->toBeFalse()
        ->and($contextPosition)->not->toBeFalse()
        ->and($coveragePosition)->toBeLessThan($contextPosition);
});
