<?php

declare(strict_types=1);

use App\Jobs\CompareBaselineToTenantJob;
use App\Models\AuditLog;
use App\Models\BaselineProfile;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Services\Baselines\BaselineAutoCloseService;
use App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator;
use App\Services\EntraAdminRoles\HighPrivilegeRoleCatalog;
use App\Services\PermissionPosture\PermissionPostureFindingGenerator;
use App\Support\Audit\AuditActionId;
use App\Support\Audit\AuditActorType;
use Carbon\CarbonImmutable;

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

if (! function_exists('automationBaselineCompareDriftItem')) {
    /**
     * @return array<string, mixed>
     */
    function automationBaselineCompareDriftItem(int $baselineProfileId, int $compareOperationRunId, string $subjectKey): array
    {
        return [
            'change_type' => 'different_version',
            'severity' => Finding::SEVERITY_MEDIUM,
            'subject_type' => 'policy',
            'subject_external_id' => $subjectKey,
            'subject_key' => $subjectKey,
            'policy_type' => 'deviceConfiguration',
            'baseline_hash' => 'baseline',
            'current_hash' => 'current',
            'evidence_fidelity' => 'meta',
            'evidence' => [
                'change_type' => 'different_version',
                'policy_type' => 'deviceConfiguration',
                'subject_key' => $subjectKey,
                'summary' => ['kind' => 'policy_snapshot'],
                'baseline' => ['policy_version_id' => null, 'hash' => 'baseline'],
                'current' => ['policy_version_id' => null, 'hash' => 'current'],
                'fidelity' => 'meta',
                'provenance' => [
                    'baseline_profile_id' => $baselineProfileId,
                    'baseline_snapshot_id' => 1,
                    'compare_operation_run_id' => $compareOperationRunId,
                    'inventory_sync_run_id' => null,
                ],
            ],
        ];
    }
}

if (! function_exists('invokeAutomationBaselineCompareUpsertFindings')) {
    /**
     * @param  array<int, array<string, mixed>>  $driftResults
     * @return array{processed_count:int,created_count:int,reopened_count:int,unchanged_count:int,seen_fingerprints:array<int,string>}
     */
    function invokeAutomationBaselineCompareUpsertFindings(
        CompareBaselineToTenantJob $job,
        \App\Models\ManagedEnvironment $tenant,
        BaselineProfile $profile,
        string $scopeKey,
        array $driftResults,
    ): array {
        $reflection = new ReflectionMethod($job, 'upsertFindings');

        /** @var array{processed_count:int,created_count:int,reopened_count:int,unchanged_count:int,seen_fingerprints:array<int,string>} $result */
        $result = $reflection->invoke($job, $tenant, $profile, $scopeKey, $driftResults);

        return $result;
    }
}

it('writes a system-origin audit row when stale drift findings auto-resolve', function (): void {
    [, $tenant] = createUserWithTenant(role: 'manager');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => (int) $tenant->workspace_id,
    ]);

    $scopeKey = 'baseline_profile:'.$profile->getKey();
    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    $observedAt = CarbonImmutable::parse('2026-03-18T09:00:00Z');
    CarbonImmutable::setTestNow($observedAt);

    $finding = Finding::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'fingerprint' => 'drift-stale-audit',
        'recurrence_key' => 'drift-stale-audit',
        'status' => Finding::STATUS_NEW,
    ]);

    expect(app(BaselineAutoCloseService::class)->resolveStaleFindings(
        tenant: $tenant,
        baselineProfileId: (int) $profile->getKey(),
        seenFingerprints: [],
        currentOperationRunId: (int) $run->getKey(),
    ))->toBe(1);

    $audit = AuditLog::query()
        ->where('resource_type', 'finding')
        ->where('resource_id', (string) $finding->getKey())
        ->where('action', AuditActionId::FindingResolved->value)
        ->latest('id')
        ->first();

    expect($audit)->not->toBeNull()
        ->and($audit->actorSnapshot()->type)->toBe(AuditActorType::System)
        ->and(data_get($audit->metadata, 'system_origin'))->toBeTrue()
        ->and(data_get($audit->metadata, 'resolved_reason'))->toBe(Finding::RESOLVE_REASON_NO_LONGER_DRIFTING);
});

it('writes system-origin audit rows for permission posture auto-resolve and recurrence reopen', function (): void {
    [, $tenant] = createUserWithTenant();

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T10:00:00Z'));
    $generator = app(PermissionPostureFindingGenerator::class);

    $generator->generate($tenant, [
        'overall_status' => 'missing',
        'permissions' => [[
            'key' => 'Perm.Automation',
            'type' => 'application',
            'status' => 'missing',
            'features' => ['policy-sync'],
        ]],
        'last_refreshed_at' => now()->toIso8601String(),
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T11:00:00Z'));
    $generator->generate($tenant, [
        'overall_status' => 'granted',
        'permissions' => [[
            'key' => 'Perm.Automation',
            'type' => 'application',
            'status' => 'granted',
            'features' => ['policy-sync'],
        ]],
        'last_refreshed_at' => now()->toIso8601String(),
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T12:00:00Z'));
    $generator->generate($tenant, [
        'overall_status' => 'missing',
        'permissions' => [[
            'key' => 'Perm.Automation',
            'type' => 'application',
            'status' => 'missing',
            'features' => ['policy-sync'],
        ]],
        'last_refreshed_at' => now()->toIso8601String(),
    ]);

    $finding = Finding::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('finding_type', Finding::FINDING_TYPE_PERMISSION_POSTURE)
        ->firstOrFail();

    $resolvedAudit = AuditLog::query()
        ->where('resource_type', 'finding')
        ->where('resource_id', (string) $finding->getKey())
        ->where('action', AuditActionId::FindingResolved->value)
        ->latest('id')
        ->first();

    $reopenedAudit = AuditLog::query()
        ->where('resource_type', 'finding')
        ->where('resource_id', (string) $finding->getKey())
        ->where('action', AuditActionId::FindingReopened->value)
        ->latest('id')
        ->first();

    expect($resolvedAudit)->not->toBeNull()
        ->and($resolvedAudit->actorSnapshot()->type)->toBe(AuditActorType::System)
        ->and(data_get($resolvedAudit->metadata, 'resolved_reason'))->toBe(Finding::RESOLVE_REASON_PERMISSION_GRANTED)
        ->and($reopenedAudit)->not->toBeNull()
        ->and($reopenedAudit->actorSnapshot()->type)->toBe(AuditActorType::System)
        ->and(data_get($reopenedAudit->metadata, 'after_status'))->toBe(Finding::STATUS_REOPENED);
});

it('writes system-origin audit rows for entra admin role auto-resolve and recurrence reopen', function (): void {
    [, $tenant] = createUserWithTenant();

    $generator = new EntraAdminRolesFindingGenerator(new HighPrivilegeRoleCatalog);

    $generator->generate($tenant, [
        'measured_at' => '2026-03-18T10:00:00Z',
        'role_definitions' => [[
            'id' => 'def-ga',
            'displayName' => 'Global Administrator',
            'templateId' => '62e90394-69f5-4237-9190-012177145e10',
            'isBuiltIn' => true,
        ]],
        'role_assignments' => [[
            'id' => 'a1',
            'roleDefinitionId' => 'def-ga',
            'principalId' => 'user-1',
            'directoryScopeId' => '/',
            'principal' => [
                '@odata.type' => '#microsoft.graph.user',
                'displayName' => 'Alice',
            ],
        ]],
    ]);

    $generator->generate($tenant, [
        'measured_at' => '2026-03-18T11:00:00Z',
        'role_definitions' => [[
            'id' => 'def-ga',
            'displayName' => 'Global Administrator',
            'templateId' => '62e90394-69f5-4237-9190-012177145e10',
            'isBuiltIn' => true,
        ]],
        'role_assignments' => [],
    ]);

    $generator->generate($tenant, [
        'measured_at' => '2026-03-18T12:00:00Z',
        'role_definitions' => [[
            'id' => 'def-ga',
            'displayName' => 'Global Administrator',
            'templateId' => '62e90394-69f5-4237-9190-012177145e10',
            'isBuiltIn' => true,
        ]],
        'role_assignments' => [[
            'id' => 'a2',
            'roleDefinitionId' => 'def-ga',
            'principalId' => 'user-1',
            'directoryScopeId' => '/',
            'principal' => [
                '@odata.type' => '#microsoft.graph.user',
                'displayName' => 'Alice Reactivated',
            ],
        ]],
    ]);

    $finding = Finding::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('finding_type', Finding::FINDING_TYPE_ENTRA_ADMIN_ROLES)
        ->where('subject_external_id', 'user-1:def-ga')
        ->firstOrFail();

    expect(AuditLog::query()
        ->where('resource_type', 'finding')
        ->where('resource_id', (string) $finding->getKey())
        ->where('action', AuditActionId::FindingResolved->value)
        ->latest('id')
        ->first()?->actorSnapshot()->type)->toBe(AuditActorType::System)
        ->and(AuditLog::query()
            ->where('resource_type', 'finding')
            ->where('resource_id', (string) $finding->getKey())
            ->where('action', AuditActionId::FindingReopened->value)
            ->latest('id')
            ->first()?->actorSnapshot()->type)->toBe(AuditActorType::System);
});

it('writes a system-origin reopen audit row for baseline recurrence handling', function (): void {
    [, $tenant] = createUserWithTenant(role: 'manager');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => (int) $tenant->workspace_id,
    ]);

    $scopeKey = 'baseline_profile:'.$profile->getKey();
    $run1 = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T08:00:00Z'));
    invokeAutomationBaselineCompareUpsertFindings(
        new CompareBaselineToTenantJob($run1),
        $tenant,
        $profile,
        $scopeKey,
        [automationBaselineCompareDriftItem((int) $profile->getKey(), (int) $run1->getKey(), 'policy-audit-recur')],
    );

    $finding = Finding::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('subject_external_id', 'policy-audit-recur')
        ->firstOrFail();

    $finding->forceFill([
        'status' => Finding::STATUS_RESOLVED,
        'resolved_at' => CarbonImmutable::parse('2026-03-18T09:00:00Z'),
        'resolved_reason' => Finding::RESOLVE_REASON_REMEDIATED,
    ])->save();

    $run2 = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T10:00:00Z'));
    invokeAutomationBaselineCompareUpsertFindings(
        new CompareBaselineToTenantJob($run2),
        $tenant,
        $profile,
        $scopeKey,
        [automationBaselineCompareDriftItem((int) $profile->getKey(), (int) $run2->getKey(), 'policy-audit-recur')],
    );

    $audit = AuditLog::query()
        ->where('resource_type', 'finding')
        ->where('resource_id', (string) $finding->getKey())
        ->where('action', AuditActionId::FindingReopened->value)
        ->latest('id')
        ->first();

    expect($audit)->not->toBeNull()
        ->and($audit->actorSnapshot()->type)->toBe(AuditActorType::System)
        ->and(data_get($audit->metadata, 'system_origin'))->toBeTrue();
});
