<?php

declare(strict_types=1);

use App\Models\Finding;
use App\Models\FindingException;
use App\Models\FindingExceptionDecision;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Workspace;
use App\Support\GovernanceDecisions\GovernanceDecisionRegisterBuilder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('builds open and recently closed decision rows from current exception truth', function (): void {
    $workspace = Workspace::factory()->create();
    $owner = User::factory()->create(['name' => 'Decision Owner']);
    $approver = User::factory()->create(['name' => 'Decision Approver']);

    $visibleTenant = Tenant::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'name' => 'Visible Tenant',
        'external_id' => 'visible-tenant',
    ]);
    $hiddenTenant = Tenant::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'name' => 'Hidden Tenant',
        'external_id' => 'hidden-tenant',
    ]);

    $pendingApproval = makeFindingExceptionWithCurrentDecision(
        tenant: $visibleTenant,
        owner: $owner,
        actor: $owner,
        status: FindingException::STATUS_PENDING,
        validityState: FindingException::VALIDITY_MISSING_SUPPORT,
        decisionType: FindingExceptionDecision::TYPE_REQUESTED,
        decisionReason: 'Pending workspace approval',
        exceptionAttributes: [
            'requested_at' => now()->subDays(2),
            'review_due_at' => now()->addDay(),
        ],
        decisionAttributes: [
            'decided_at' => now()->subDays(2),
        ],
    );

    $followUpNeeded = makeFindingExceptionWithCurrentDecision(
        tenant: $visibleTenant,
        owner: $owner,
        actor: $approver,
        status: FindingException::STATUS_EXPIRING,
        validityState: FindingException::VALIDITY_EXPIRING,
        decisionType: FindingExceptionDecision::TYPE_APPROVED,
        decisionReason: 'Approved until remediation completes',
        exceptionAttributes: [
            'approved_by_user_id' => (int) $approver->getKey(),
            'approved_at' => now()->subDays(5),
            'effective_from' => now()->subDays(5),
            'expires_at' => now()->addDays(2),
            'review_due_at' => now()->addDay(),
        ],
        decisionAttributes: [
            'decided_at' => now()->subDays(5),
        ],
    );

    $recentlyRejected = makeFindingExceptionWithCurrentDecision(
        tenant: $visibleTenant,
        owner: $owner,
        actor: $approver,
        status: FindingException::STATUS_REJECTED,
        validityState: FindingException::VALIDITY_REJECTED,
        decisionType: FindingExceptionDecision::TYPE_REJECTED,
        decisionReason: 'Evidence bundle was incomplete',
        exceptionAttributes: [
            'approved_by_user_id' => (int) $approver->getKey(),
            'rejected_at' => now()->subDays(3),
            'review_due_at' => now()->subDays(4),
        ],
        decisionAttributes: [
            'decided_at' => now()->subDays(3),
        ],
    );

    makeFindingExceptionWithCurrentDecision(
        tenant: $visibleTenant,
        owner: $owner,
        actor: $approver,
        status: FindingException::STATUS_REVOKED,
        validityState: FindingException::VALIDITY_REVOKED,
        decisionType: FindingExceptionDecision::TYPE_REVOKED,
        decisionReason: 'Closed long ago',
        exceptionAttributes: [
            'approved_by_user_id' => (int) $approver->getKey(),
            'revoked_at' => now()->subDays(45),
            'review_due_at' => now()->subDays(46),
        ],
        decisionAttributes: [
            'decided_at' => now()->subDays(45),
        ],
    );

    makeFindingExceptionWithCurrentDecision(
        tenant: $hiddenTenant,
        owner: $owner,
        actor: $owner,
        status: FindingException::STATUS_PENDING,
        validityState: FindingException::VALIDITY_MISSING_SUPPORT,
        decisionType: FindingExceptionDecision::TYPE_REQUESTED,
        decisionReason: 'Hidden tenant request',
        exceptionAttributes: [
            'requested_at' => now()->subDay(),
            'review_due_at' => now()->addDays(2),
        ],
        decisionAttributes: [
            'decided_at' => now()->subDay(),
        ],
    );

    $builder = app(GovernanceDecisionRegisterBuilder::class);

    $openPayload = $builder->build(
        workspace: $workspace,
        visibleTenants: [$visibleTenant],
        registerState: 'open',
    );

    $openRows = collect($openPayload['rows'])->keyBy('exception_id');

    expect($openPayload['counts'])->toMatchArray([
        'open' => 2,
        'recently_closed' => 1,
    ])
        ->and($openRows->keys()->all())->toBe([
            (int) $pendingApproval->getKey(),
            (int) $followUpNeeded->getKey(),
        ])
        ->and($openRows[(int) $pendingApproval->getKey()]['tenant_name'])->toBe('Visible Tenant')
        ->and($openRows[(int) $pendingApproval->getKey()]['owner_name'])->toBe('Decision Owner')
        ->and($openRows[(int) $pendingApproval->getKey()]['next_action_label'])->toBe('Review approval')
        ->and($openRows[(int) $followUpNeeded->getKey()]['next_action_label'])->toBe('Review follow-up');

    $recentlyClosedPayload = $builder->build(
        workspace: $workspace,
        visibleTenants: [$visibleTenant],
        registerState: 'recently_closed',
    );

    expect($recentlyClosedPayload['counts'])->toMatchArray([
        'open' => 2,
        'recently_closed' => 1,
    ])
        ->and(collect($recentlyClosedPayload['rows'])->pluck('exception_id')->all())->toBe([
            (int) $recentlyRejected->getKey(),
        ])
        ->and($recentlyClosedPayload['rows'][0]['closure_reason'])->toBe('Evidence bundle was incomplete')
        ->and($recentlyClosedPayload['rows'][0]['status'])->toBe(FindingException::STATUS_REJECTED);
});

it('keeps missing owner visible instead of omitting follow-up-needed rows', function (): void {
    $workspace = Workspace::factory()->create();
    $requester = User::factory()->create();
    $tenant = Tenant::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
    ]);

    $unownedException = makeFindingExceptionWithCurrentDecision(
        tenant: $tenant,
        owner: null,
        actor: $requester,
        status: FindingException::STATUS_EXPIRED,
        validityState: FindingException::VALIDITY_EXPIRED,
        decisionType: FindingExceptionDecision::TYPE_APPROVED,
        decisionReason: 'Expired and needs a fresh decision',
        exceptionAttributes: [
            'requested_by_user_id' => (int) $requester->getKey(),
            'owner_user_id' => null,
            'approved_by_user_id' => (int) $requester->getKey(),
            'approved_at' => now()->subDays(20),
            'effective_from' => now()->subDays(20),
            'expires_at' => now()->subDay(),
            'review_due_at' => now()->subDays(2),
        ],
        decisionAttributes: [
            'decided_at' => now()->subDays(20),
        ],
    );

    $payload = app(GovernanceDecisionRegisterBuilder::class)->build(
        workspace: $workspace,
        visibleTenants: [$tenant],
        registerState: 'open',
    );

    expect($payload['rows'])->toHaveCount(1)
        ->and($payload['rows'][0]['exception_id'])->toBe((int) $unownedException->getKey())
        ->and($payload['rows'][0]['owner_name'])->toBeNull()
        ->and($payload['rows'][0]['next_action_label'])->toBe('Review follow-up');
});

/**
 * @param  array<string, mixed>  $exceptionAttributes
 * @param  array<string, mixed>  $decisionAttributes
 */
function makeFindingExceptionWithCurrentDecision(
    Tenant $tenant,
    ?User $owner,
    User $actor,
    string $status,
    string $validityState,
    string $decisionType,
    string $decisionReason,
    array $exceptionAttributes = [],
    array $decisionAttributes = [],
): FindingException {
    $requesterId = $exceptionAttributes['requested_by_user_id'] ?? (int) $actor->getKey();

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

    $exception = FindingException::query()->create(array_merge([
        'workspace_id' => (int) $tenant->workspace_id,
        'tenant_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $finding->getKey(),
        'requested_by_user_id' => $requesterId,
        'owner_user_id' => $owner?->getKey(),
        'status' => $status,
        'current_validity_state' => $validityState,
        'request_reason' => 'Decision register test setup',
        'requested_at' => now()->subDays(7),
        'review_due_at' => now()->addDays(7),
        'evidence_summary' => ['reference_count' => 0],
    ], $exceptionAttributes));

    $decision = $exception->decisions()->create(array_merge([
        'workspace_id' => (int) $tenant->workspace_id,
        'tenant_id' => (int) $tenant->getKey(),
        'actor_user_id' => (int) $actor->getKey(),
        'decision_type' => $decisionType,
        'reason' => $decisionReason,
        'metadata' => [],
        'decided_at' => now()->subDays(7),
    ], $decisionAttributes));

    $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save();

    return $exception->fresh(['tenant', 'owner', 'currentDecision']);
}