<?php

declare(strict_types=1);

use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\OperationRun;
use App\Support\Baselines\BaselineCompareStats;

it('returns no_tenant state when environment is null', function (): void {
    $stats = BaselineCompareStats::forTenant(null);

    expect($stats->state)->toBe('no_tenant')
        ->and($stats->message)->toContain('No environment');
});

it('returns no_assignment state when tenant has no baseline assignment', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('no_assignment')
        ->and($stats->profileName)->toBeNull()
        ->and($stats->message)->toBe('This environment has no baseline assignment. A workspace manager can assign a baseline profile to this environment.');
});

it('returns no_snapshot state when profile has no consumable snapshot', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('no_snapshot')
        ->and($stats->profileName)->toBe($profile->name)
        ->and($stats->reasonCode)->toBe('baseline.compare.no_consumable_snapshot');
});

it('falls back to the latest complete snapshot when a newer attempt is incomplete', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $completeSnapshot = BaselineSnapshot::factory()->complete()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
        'captured_at' => now()->subHour(),
        'completed_at' => now()->subHour(),
    ]);

    BaselineSnapshot::factory()->incomplete()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
        'captured_at' => now(),
    ]);

    $profile->update(['active_snapshot_id' => $completeSnapshot->getKey()]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('idle')
        ->and($stats->snapshotId)->toBe((int) $completeSnapshot->getKey())
        ->and($stats->reasonCode)->toBeNull();
});

it('reports building as the blocking reason when no complete snapshot exists yet', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    BaselineSnapshot::factory()->building()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('no_snapshot')
        ->and($stats->reasonCode)->toBe('baseline.compare.snapshot_building')
        ->and($stats->reasonMessage)->toContain('building');
});

it('returns comparing state when a run is queued', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'type' => 'baseline_compare',
        'status' => 'queued',
        'outcome' => 'pending',
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('comparing')
        ->and($stats->operationRunId)->not->toBeNull();
});

it('returns failed state when the latest run has failed outcome', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'failed',
        'failure_summary' => ['message' => 'Graph API timeout'],
        'completed_at' => now()->subHour(),
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('failed')
        ->and($stats->failureReason)->toBe('Graph API timeout')
        ->and($stats->operationRunId)->not->toBeNull()
        ->and($stats->lastComparedHuman)->not->toBeNull()
        ->and($stats->lastComparedIso)->not->toBeNull();
});

it('returns ready state with grouped severity counts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    Finding::factory()->count(2)->create([
        'workspace_id' => $tenant->workspace_id,
        'managed_environment_id' => $tenant->getKey(),
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_NEW,
    ]);

    Finding::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'managed_environment_id' => $tenant->getKey(),
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'severity' => Finding::SEVERITY_MEDIUM,
        'status' => Finding::STATUS_NEW,
    ]);

    Finding::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'managed_environment_id' => $tenant->getKey(),
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'severity' => Finding::SEVERITY_LOW,
        'status' => Finding::STATUS_NEW,
    ]);

    // Terminal finding should not be counted in "open" drift totals.
    Finding::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'managed_environment_id' => $tenant->getKey(),
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_RESOLVED,
    ]);

    OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'succeeded',
        'completed_at' => now()->subHours(2),
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('ready')
        ->and($stats->findingsCount)->toBe(4)
        ->and($stats->severityCounts)->toBe([
            'high' => 2,
            'medium' => 1,
            'low' => 1,
        ])
        ->and($stats->lastComparedHuman)->not->toBeNull()
        ->and($stats->lastComparedIso)->toContain('T');
});

it('returns idle state when profile is ready but no run exists yet', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->state)->toBe('idle')
        ->and($stats->profileName)->toBe($profile->name)
        ->and($stats->snapshotId)->toBe((int) $snapshot->getKey());
});

it('forWidget returns grouped severity counts for new findings only', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    // New finding (should be counted)
    Finding::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'managed_environment_id' => $tenant->getKey(),
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_NEW,
    ]);

    // Resolved finding (should NOT be counted)
    Finding::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'managed_environment_id' => $tenant->getKey(),
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
        'source' => 'baseline.compare',
        'scope_key' => $scopeKey,
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_RESOLVED,
    ]);

    $stats = BaselineCompareStats::forWidget($tenant);

    expect($stats->findingsCount)->toBe(1)
        ->and($stats->severityCounts['high'])->toBe(1);
});

it('returns governance attention counts from current findings truth', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => (int) $snapshot->getKey()]);

    BaselineTenantAssignment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'baseline_profile_id' => (int) $profile->getKey(),
    ]);

    OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'succeeded',
        'completed_at' => now(),
        'context' => [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'baseline_compare' => [
                'coverage' => [
                    'effective_types' => ['deviceConfiguration'],
                    'covered_types' => ['deviceConfiguration'],
                    'uncovered_types' => [],
                    'proof' => true,
                ],
            ],
        ],
    ]);

    Finding::factory()->triaged()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'due_at' => now()->subDay(),
    ]);

    $expiringFinding = Finding::factory()->riskAccepted()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
    ]);

    FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $expiringFinding->getKey(),
        'requested_by_user_id' => (int) $user->getKey(),
        'owner_user_id' => (int) $user->getKey(),
        'approved_by_user_id' => (int) $user->getKey(),
        'status' => FindingException::STATUS_EXPIRING,
        'current_validity_state' => FindingException::VALIDITY_EXPIRING,
        'request_reason' => 'Expiring governance coverage',
        'approval_reason' => 'Approved for coverage',
        'requested_at' => now()->subDays(2),
        'approved_at' => now()->subDay(),
        'effective_from' => now()->subDay(),
        'expires_at' => now()->addDays(2),
        'review_due_at' => now()->addDay(),
        'evidence_summary' => ['reference_count' => 0],
    ]);

    $lapsedFinding = Finding::factory()->riskAccepted()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
    ]);

    FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $lapsedFinding->getKey(),
        'requested_by_user_id' => (int) $user->getKey(),
        'owner_user_id' => (int) $user->getKey(),
        'approved_by_user_id' => (int) $user->getKey(),
        'status' => FindingException::STATUS_EXPIRED,
        'current_validity_state' => FindingException::VALIDITY_EXPIRED,
        'request_reason' => 'Expired governance coverage',
        'approval_reason' => 'Approved for coverage',
        'requested_at' => now()->subDays(3),
        'approved_at' => now()->subDays(2),
        'effective_from' => now()->subDays(2),
        'expires_at' => now()->subDay(),
        'review_due_at' => now()->subDay(),
        'evidence_summary' => ['reference_count' => 0],
    ]);

    Finding::factory()->inProgress()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
    ]);

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

    $stats = BaselineCompareStats::forTenant($tenant);

    expect($stats->overdueOpenFindingsCount)->toBe(1)
        ->and($stats->expiringGovernanceCount)->toBe(1)
        ->and($stats->lapsedGovernanceCount)->toBe(1)
        ->and($stats->activeNonNewFindingsCount)->toBe(2)
        ->and($stats->highSeverityActiveFindingsCount)->toBe(1);
});
