<?php

declare(strict_types=1);

namespace App\Support\GovernanceDecisions;

use App\Models\FindingException;
use App\Models\FindingExceptionDecision;
use App\Models\Tenant;
use App\Models\Workspace;
use Carbon\CarbonInterface;
use Illuminate\Support\Collection;

final readonly class GovernanceDecisionRegisterBuilder
{
    private const int RECENTLY_CLOSED_DAYS = 30;

    /**
     * @var list<string>
     */
    private const array TERMINAL_STATUSES = [
        FindingException::STATUS_REJECTED,
        FindingException::STATUS_REVOKED,
        FindingException::STATUS_SUPERSEDED,
    ];

    /**
     * @param  array<int, Tenant>  $visibleTenants
     * @return array{
     *     rows: list<array<string, mixed>>,
     *     counts: array{open: int, recently_closed: int},
     * }
     */
    public function build(Workspace $workspace, array $visibleTenants, string $registerState = 'open'): array
    {
        $visibleTenantIds = array_values(array_map(
            static fn (Tenant $tenant): int => (int) $tenant->getKey(),
            $visibleTenants,
        ));

        if ($visibleTenantIds === []) {
            return [
                'rows' => [],
                'counts' => [
                    'open' => 0,
                    'recently_closed' => 0,
                ],
            ];
        }

        $rows = FindingException::query()
            ->where('workspace_id', (int) $workspace->getKey())
            ->whereIn('tenant_id', $visibleTenantIds)
            ->with(['tenant:id,name', 'owner:id,name', 'currentDecision'])
            ->get()
            ->map(fn (FindingException $exception): ?array => $this->buildRow($exception))
            ->filter()
            ->values();

        /** @var Collection<int, array<string, mixed>> $openRows */
        $openRows = $rows
            ->where('register_state', 'open')
            ->sortBy([
                ['due_at', 'asc'],
                ['exception_id', 'asc'],
            ])
            ->values();

        /** @var Collection<int, array<string, mixed>> $recentlyClosedRows */
        $recentlyClosedRows = $rows
            ->where('register_state', 'recently_closed')
            ->sortByDesc('decision_at')
            ->values();

        return [
            'rows' => match ($registerState) {
                'recently_closed' => $recentlyClosedRows->all(),
                default => $openRows->all(),
            },
            'counts' => [
                'open' => $openRows->count(),
                'recently_closed' => $recentlyClosedRows->count(),
            ],
        ];
    }

    /**
     * @return array<string, mixed>|null
     */
    private function buildRow(FindingException $exception): ?array
    {
        $currentDecision = $exception->currentDecision;

        if (! $currentDecision instanceof FindingExceptionDecision) {
            return null;
        }

        $registerState = $this->resolveRegisterState($exception, $currentDecision);

        if ($registerState === null) {
            return null;
        }

        return [
            'exception_id' => (int) $exception->getKey(),
            'register_state' => $registerState,
            'tenant_name' => $exception->tenant?->name,
            'owner_name' => $exception->owner?->name,
            'status' => (string) $exception->status,
            'current_validity_state' => (string) $exception->current_validity_state,
            'next_action_label' => $registerState === 'open'
                ? $this->resolveNextActionLabel($exception, $currentDecision)
                : 'Decision closed',
            'closure_reason' => $registerState === 'recently_closed'
                ? (string) $currentDecision->reason
                : null,
            'due_at' => $exception->review_due_at ?? $exception->expires_at,
            'decision_at' => $currentDecision->decided_at,
        ];
    }

    private function resolveRegisterState(FindingException $exception, FindingExceptionDecision $currentDecision): ?string
    {
        $status = (string) $exception->status;

        if (in_array($status, self::TERMINAL_STATUSES, true)) {
            return $this->isRecentlyClosed($currentDecision->decided_at)
                ? 'recently_closed'
                : null;
        }

        return 'open';
    }

    private function resolveNextActionLabel(FindingException $exception, FindingExceptionDecision $currentDecision): string
    {
        if ($exception->isPendingRenewal() || $currentDecision->decision_type === FindingExceptionDecision::TYPE_RENEWAL_REQUESTED) {
            return 'Review renewal';
        }

        if ($exception->isPending()) {
            return 'Review approval';
        }

        return 'Review follow-up';
    }

    private function isRecentlyClosed(?CarbonInterface $decidedAt): bool
    {
        if (! $decidedAt instanceof CarbonInterface) {
            return false;
        }

        return $decidedAt->greaterThanOrEqualTo(now()->startOfDay()->subDays(self::RECENTLY_CLOSED_DAYS));
    }
}