<?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\Models\ManagedEnvironment;
use App\Models\User;
use App\Support\Baselines\BaselineCompareReasonCode;
use App\Support\Baselines\BaselineCompareSummaryAssessment;
use App\Support\Baselines\TenantGovernanceAggregateResolver;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;

function createTenantGovernanceAggregateTenant(): array
{
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $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(),
    ]);

    return [$user, $tenant, $profile, $snapshot];
}

/**
 * @param  array<string, mixed>  $attributes
 * @param  array<string, mixed>  $compareContext
 */
function seedTenantGovernanceAggregateRun(ManagedEnvironment $tenant, BaselineProfile $profile, BaselineSnapshot $snapshot, array $attributes = [], array $compareContext = []): OperationRun
{
    return OperationRun::factory()->create(array_replace_recursive([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => OperationRunType::BaselineCompare->value,
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Succeeded->value,
        'completed_at' => now(),
        'context' => [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'baseline_compare' => array_replace_recursive([
                'reason_code' => BaselineCompareReasonCode::NoDriftDetected->value,
                'coverage' => [
                    'effective_types' => ['deviceConfiguration'],
                    'covered_types' => ['deviceConfiguration'],
                    'uncovered_types' => [],
                    'proof' => true,
                ],
            ], $compareContext),
        ],
    ], $attributes));
}

function createTenantGovernanceException(ManagedEnvironment $tenant, Finding $finding, User $user, string $status, string $validityState, ?\Carbon\CarbonInterface $expiresAt = null): void
{
    FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $finding->getKey(),
        'requested_by_user_id' => (int) $user->getKey(),
        'owner_user_id' => (int) $user->getKey(),
        'approved_by_user_id' => (int) $user->getKey(),
        'status' => $status,
        'current_validity_state' => $validityState,
        'request_reason' => 'Exception created for tenant governance aggregate coverage',
        'approval_reason' => 'Approved for test coverage',
        'requested_at' => now()->subDays(2),
        'approved_at' => now()->subDay(),
        'effective_from' => now()->subDay(),
        'expires_at' => $expiresAt,
        'review_due_at' => now()->addDay(),
        'evidence_summary' => ['reference_count' => 0],
    ]);
}

it('resolves an unavailable governance aggregate when the assigned baseline has no snapshot', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => (int) $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(),
    ]);

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->compareState)->toBe('no_snapshot')
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_UNAVAILABLE)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING)
        ->and($aggregate?->headline)->toBe('The current baseline snapshot is not available for compare.');
});

it('resolves an in-progress governance aggregate for queued compare runs', function (): void {
    [, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun(
        tenant: $tenant,
        profile: $profile,
        snapshot: $snapshot,
        attributes: [
            'status' => OperationRunStatus::Queued->value,
            'outcome' => OperationRunOutcome::Pending->value,
            'completed_at' => null,
        ],
    );

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->compareState)->toBe('comparing')
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_IN_PROGRESS)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_RUN)
        ->and($aggregate?->headline)->toBe('Baseline compare is in progress.');
});

it('resolves a failed governance aggregate with run follow-up', function (): void {
    [, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun(
        tenant: $tenant,
        profile: $profile,
        snapshot: $snapshot,
        attributes: [
            'outcome' => OperationRunOutcome::Failed->value,
            'failure_summary' => ['message' => 'Graph API timeout'],
        ],
    );

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_RUN)
        ->and($aggregate?->nextActionLabel)->toBe('Review the failed operation')
        ->and($aggregate?->headline)->toBe('The latest baseline compare failed before it produced a usable result.');
});

it('resolves an action-required aggregate when open drift findings remain', function (): void {
    [, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun($tenant, $profile, $snapshot);

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

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED)
        ->and($aggregate?->visibleDriftFindingsCount)->toBe(1)
        ->and($aggregate?->highSeverityActiveFindingsCount)->toBe(1)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_FINDINGS);
});

it('resolves overdue workflow pressure as action required even with zero visible drift', function (): void {
    [$user, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun($tenant, $profile, $snapshot);

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

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED)
        ->and($aggregate?->overdueOpenFindingsCount)->toBe(1)
        ->and($aggregate?->visibleDriftFindingsCount)->toBe(0)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_FINDINGS)
        ->and($aggregate?->headline)->toContain('overdue finding');
});

it('resolves lapsed governance as action required even with zero visible drift', function (): void {
    [$user, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun($tenant, $profile, $snapshot);

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

    createTenantGovernanceException(
        tenant: $tenant,
        finding: $finding,
        user: $user,
        status: FindingException::STATUS_EXPIRED,
        validityState: FindingException::VALIDITY_EXPIRED,
        expiresAt: now()->subDay(),
    );

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED)
        ->and($aggregate?->lapsedGovernanceCount)->toBe(1)
        ->and($aggregate?->visibleDriftFindingsCount)->toBe(0)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_FINDINGS)
        ->and($aggregate?->headline)->toContain('Accepted-risk governance has lapsed');
});

it('resolves expiring governance into the shared action-required contract', function (): void {
    [$user, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun($tenant, $profile, $snapshot);

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

    createTenantGovernanceException(
        tenant: $tenant,
        finding: $finding,
        user: $user,
        status: FindingException::STATUS_EXPIRING,
        validityState: FindingException::VALIDITY_EXPIRING,
        expiresAt: now()->addDays(2),
    );

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED)
        ->and($aggregate?->expiringGovernanceCount)->toBe(1)
        ->and($aggregate?->nextActionLabel)->toBe('Open findings');
});

it('resolves limited-confidence zero findings into a caution aggregate', function (): void {
    [, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun(
        tenant: $tenant,
        profile: $profile,
        snapshot: $snapshot,
        attributes: [
            'outcome' => OperationRunOutcome::PartiallySucceeded->value,
        ],
        compareContext: [
            'reason_code' => BaselineCompareReasonCode::CoverageUnproven->value,
            'coverage' => [
                'effective_types' => ['deviceConfiguration', 'deviceCompliancePolicy'],
                'covered_types' => ['deviceConfiguration'],
                'uncovered_types' => ['deviceCompliancePolicy'],
                'proof' => false,
            ],
        ],
    );

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_CAUTION)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_RUN)
        ->and($aggregate?->headline)->toBe('The last compare finished, but normal result output was suppressed.');
});

it('resolves stale no-drift compare results into a stale aggregate', function (): void {
    [, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun(
        tenant: $tenant,
        profile: $profile,
        snapshot: $snapshot,
        attributes: [
            'completed_at' => now()->subDays(10),
        ],
    );

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_STALE)
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_LANDING)
        ->and($aggregate?->headline)->toBe('The latest baseline compare result is stale.');
});

it('resolves trustworthy no-drift results into a positive all-clear aggregate', function (): void {
    [, $tenant, $profile, $snapshot] = createTenantGovernanceAggregateTenant();

    seedTenantGovernanceAggregateRun($tenant, $profile, $snapshot);

    $aggregate = app(TenantGovernanceAggregateResolver::class)->forTenant($tenant);

    expect($aggregate)->not->toBeNull()
        ->and($aggregate?->stateFamily)->toBe(BaselineCompareSummaryAssessment::STATE_POSITIVE)
        ->and($aggregate?->positiveClaimAllowed)->toBeTrue()
        ->and($aggregate?->nextActionTarget)->toBe(BaselineCompareSummaryAssessment::NEXT_TARGET_NONE)
        ->and($aggregate?->headline)->toBe('No confirmed drift in the latest baseline compare.');
});
