<?php

declare(strict_types=1);

namespace App\Services\Baselines\SnapshotRendering;

use App\Models\BaselineSnapshot;
use App\Models\BaselineSnapshotItem;
use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Governance\PlatformSubjectDescriptorNormalizer;
use App\Support\Inventory\InventoryPolicyTypeMeta;
use App\Support\Ui\EnterpriseDetail\EnterpriseDetailBuilder;
use App\Support\Ui\EnterpriseDetail\EnterpriseDetailPageData;
use App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory;
use App\Support\Ui\EnterpriseDetail\SummaryHeaderData;
use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthEnvelope;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Throwable;

final class BaselineSnapshotPresenter
{
    public function __construct(
        private readonly SnapshotTypeRendererRegistry $registry,
    ) {}

    public function present(BaselineSnapshot $snapshot): RenderedSnapshot
    {
        $snapshot->loadMissing(['baselineProfile', 'items']);

        $summary = is_array($snapshot->summary_jsonb) ? $snapshot->summary_jsonb : [];
        $items = $snapshot->items instanceof EloquentCollection
            ? $snapshot->items->sortBy([
                ['policy_type', 'asc'],
                ['id', 'asc'],
            ])->values()
            : collect();

        $groups = $items
            ->groupBy(static fn (BaselineSnapshotItem $item): string => (string) $item->policy_type)
            ->map(fn (Collection $groupItems, string $policyType): RenderedSnapshotGroup => $this->presentGroup($policyType, $groupItems))
            ->sortBy(static fn (RenderedSnapshotGroup $group): string => mb_strtolower($group->label))
            ->values()
            ->all();

        $summaryRows = array_map(
            static fn (RenderedSnapshotGroup $group): array => [
                'policyType' => $group->policyType,
                'label' => $group->label,
                'governedSubjectLabel' => data_get($group->subjectDescriptor, 'display_label', $group->label),
                'subjectDescriptor' => $group->subjectDescriptor,
                'itemCount' => $group->itemCount,
                'fidelity' => $group->fidelity->value,
                'gapCount' => $group->gapSummary->count,
                'capturedAt' => $group->capturedAt,
                'coverageHint' => $group->coverageHint,
            ],
            $groups,
        );

        $overallGapSummary = $this->summaryGapSummary($summary);
        $overallGapCount = $overallGapSummary->count;
        $overallFidelity = FidelityState::fromSummary($summary, $items->isNotEmpty());

        return new RenderedSnapshot(
            snapshotId: (int) $snapshot->getKey(),
            baselineProfileName: $snapshot->baselineProfile?->name,
            capturedAt: $snapshot->captured_at?->toIso8601String(),
            snapshotIdentityHash: is_string($snapshot->snapshot_identity_hash) && trim($snapshot->snapshot_identity_hash) !== ''
                ? trim($snapshot->snapshot_identity_hash)
                : null,
            stateLabel: $this->gapStatusSpec($overallGapCount)->label,
            fidelitySummary: $this->fidelitySummary($summary),
            overallFidelity: $overallFidelity,
            overallGapCount: $overallGapCount,
            summaryRows: $summaryRows,
            groups: $groups,
            technicalDetail: [
                'defaultCollapsed' => true,
                'summaryPayload' => $summary,
                'groupPayloads' => array_map(
                    static fn (RenderedSnapshotGroup $group): array => [
                        'label' => $group->label,
                        'renderingError' => $group->renderingError,
                        'payload' => $group->technicalPayload,
                    ],
                    $groups,
                ),
            ],
            hasItems: $items->isNotEmpty(),
        );
    }

    /**
     * @param  list<array<string, mixed>>  $relatedContext
     */
    public function presentEnterpriseDetail(BaselineSnapshot $snapshot, array $relatedContext = []): EnterpriseDetailPageData
    {
        $rendered = $this->present($snapshot);
        $factory = new EnterpriseDetailSectionFactory;
        $truthPresenter = app(ArtifactTruthPresenter::class);
        $truth = $truthPresenter->forBaselineSnapshot($snapshot);
        $compressedOutcome = $truthPresenter->compressedOutcomeFor($snapshot, SurfaceCompressionContext::baselineSnapshot());

        $truthBadge = $factory->statusBadge(
            $truth->primaryBadgeSpec()->label,
            $truth->primaryBadgeSpec()->color,
            $truth->primaryBadgeSpec()->icon,
            $truth->primaryBadgeSpec()->iconColor,
        );

        $lifecycleSpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotLifecycle, $snapshot->lifecycleState()->value);
        $lifecycleBadge = $factory->statusBadge(
            $lifecycleSpec->label,
            $lifecycleSpec->color,
            $lifecycleSpec->icon,
            $lifecycleSpec->iconColor,
        );

        $fidelitySpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotFidelity, $rendered->overallFidelity->value);
        $fidelityBadge = $factory->statusBadge(
            $fidelitySpec->label,
            $fidelitySpec->color,
            $fidelitySpec->icon,
            $fidelitySpec->iconColor,
        );

        $capturedItemCount = array_sum(array_map(
            static fn (array $row): int => (int) ($row['itemCount'] ?? 0),
            $rendered->summaryRows,
        ));
        $currentTruth = $this->currentTruthPresentation($truth);
        $currentTruthBadge = $factory->statusBadge(
            $currentTruth['label'],
            $currentTruth['color'],
            $currentTruth['icon'],
            $currentTruth['iconColor'],
        );
        $operatorExplanation = $truth->operatorExplanation;
        $primaryOutcomeLabel = $compressedOutcome?->primaryLabel ?? $truth->primaryLabel;
        $primaryOutcomeReason = $compressedOutcome?->primaryReason;
        $nextActionText = $compressedOutcome?->nextActionText ?? $truth->nextStepText();
        $capturedItemLabel = sprintf(
            '%d captured item%s',
            $capturedItemCount,
            $capturedItemCount === 1 ? '' : 's',
        );
        $gapSummaryLine = match (true) {
            $capturedItemCount === 0 => 'No governed subjects were captured for this baseline snapshot yet.',
            $rendered->overallGapCount > 0 => sprintf(
                '%s with %d evidence gap%s still needing review.',
                $capturedItemLabel,
                $rendered->overallGapCount,
                $rendered->overallGapCount === 1 ? '' : 's',
            ),
            default => $capturedItemLabel.' with no recorded evidence gaps.',
        };

        return EnterpriseDetailBuilder::make('baseline_snapshot', 'workspace')
            ->header(new SummaryHeaderData(
                title: $rendered->baselineProfileName ?? 'Baseline snapshot',
                subtitle: 'Snapshot #'.$rendered->snapshotId,
                statusBadges: [$truthBadge, $lifecycleBadge, $fidelityBadge],
                keyFacts: [
                    $factory->keyFact('Current truth', $currentTruth['label'], badge: $currentTruthBadge),
                    $factory->keyFact('Next step', $nextActionText),
                    $factory->keyFact('Captured', $this->formatTimestamp($rendered->capturedAt)),
                    $factory->keyFact('Captured items', $capturedItemCount),
                ],
                descriptionHint: 'Outcome, short reason, and next step stay ahead of lifecycle proof and technical payload detail.',
            ))
            ->decisionZone($factory->decisionZone(
                facts: array_values(array_filter([
                    $factory->keyFact('Outcome', $primaryOutcomeLabel, badge: $truthBadge),
                    $primaryOutcomeReason !== null
                        ? $factory->keyFact('Why this matters', $primaryOutcomeReason)
                        : null,
                    $factory->keyFact('Current truth', $currentTruth['label'], badge: $currentTruthBadge),
                    $operatorExplanation !== null
                        ? $factory->keyFact('Result meaning', $operatorExplanation->evaluationResultLabel())
                        : null,
                    $operatorExplanation !== null
                        ? $factory->keyFact('Result trust', $operatorExplanation->trustworthinessLabel())
                        : null,
                    $factory->keyFact('Lifecycle', $lifecycleSpec->label, badge: $lifecycleBadge),
                    $operatorExplanation !== null && $operatorExplanation->coverageStatement !== null
                        ? $factory->keyFact('Coverage', $operatorExplanation->coverageStatement)
                        : null,
                ])),
                primaryNextStep: $factory->primaryNextStep(
                    $nextActionText,
                    'Baseline snapshot',
                ),
                description: 'Start here to judge whether this baseline snapshot is usable for downstream compare or review work before reading lifecycle proof and technical payload detail.',
                compactCounts: $factory->countPresentation(
                    summaryLine: $gapSummaryLine,
                    primaryFacts: [
                        $factory->keyFact('Captured items', $capturedItemCount),
                        $factory->keyFact('Evidence gaps', $rendered->overallGapCount),
                    ],
                ),
                title: 'Outcome summary',
            ))
            ->addSection(
                $factory->viewSection(
                    id: 'artifact_truth',
                    kind: 'current_status',
                    title: 'Outcome details',
                    view: 'filament.infolists.entries.governance-artifact-truth',
                    viewData: [
                        'artifactTruthState' => $truth->toArray($compressedOutcome),
                        'surface' => 'expanded',
                    ],
                    description: 'Detailed outcome diagnostics stay secondary to the primary operator summary above.',
                ),
                $factory->viewSection(
                    id: 'coverage_summary',
                    kind: 'current_status',
                    title: 'Coverage summary',
                    view: 'filament.infolists.entries.baseline-snapshot-summary-table',
                    viewData: ['rows' => $rendered->summaryRows],
                    description: $rendered->fidelitySummary,
                    emptyState: $factory->emptyState('No captured governed subjects are available in this snapshot.'),
                ),
                $factory->viewSection(
                    id: 'related_context',
                    kind: 'related_context',
                    title: 'Related context',
                    view: 'filament.infolists.entries.related-context',
                    viewData: ['entries' => $relatedContext],
                    emptyState: $factory->emptyState('No related context is available for this record.'),
                ),
                $factory->viewSection(
                    id: 'captured_policy_types',
                    kind: 'domain_detail',
                    title: 'Captured governed subjects',
                    view: 'filament.infolists.entries.baseline-snapshot-groups',
                    viewData: ['groups' => array_map(
                        static fn (RenderedSnapshotGroup $group): array => $group->toArray(),
                        $rendered->groups,
                    )],
                    emptyState: $factory->emptyState('No snapshot items were captured for this baseline snapshot.'),
                ),
            )
            ->addSupportingCard(
                $factory->supportingFactsCard(
                    kind: 'status',
                    title: 'Snapshot status',
                    items: [
                        $factory->keyFact('Outcome', $primaryOutcomeLabel, badge: $truthBadge),
                        $factory->keyFact('Current truth', $currentTruth['label'], badge: $currentTruthBadge),
                        $factory->keyFact('Lifecycle', $lifecycleSpec->label, badge: $lifecycleBadge),
                    ],
                ),
                $factory->supportingFactsCard(
                    kind: 'coverage',
                    title: 'Coverage',
                    items: [
                        $factory->keyFact('Overall fidelity', $fidelitySpec->label, badge: $fidelityBadge),
                        $factory->keyFact('Fidelity mix', $rendered->fidelitySummary),
                        $factory->keyFact('Evidence gaps', $rendered->overallGapCount),
                        $factory->keyFact('Captured items', $capturedItemCount),
                    ],
                ),
                $factory->supportingFactsCard(
                    kind: 'timestamps',
                    title: 'Capture timing',
                    items: [
                        $factory->keyFact('Captured', $this->formatTimestamp($rendered->capturedAt)),
                        $factory->keyFact('Completed', $this->formatTimestamp($snapshot->completed_at?->toIso8601String())),
                        $factory->keyFact('Failed', $this->formatTimestamp($snapshot->failed_at?->toIso8601String())),
                        $factory->keyFact('Identity hash', $rendered->snapshotIdentityHash),
                    ],
                ),
            )
            ->addTechnicalSection(
                $factory->technicalDetail(
                    title: 'Technical detail',
                    entries: [
                        $factory->keyFact('Identity hash', $rendered->snapshotIdentityHash),
                    ],
                    description: 'Technical payloads are secondary on purpose. Use them for debugging capture fidelity and renderer fallbacks.',
                    view: 'filament.infolists.entries.baseline-snapshot-technical-detail',
                    viewData: ['technical' => $rendered->technicalDetail],
                ),
            )
            ->build();
    }

    /**
     * @param  Collection<int, BaselineSnapshotItem>  $items
     */
    private function presentGroup(string $policyType, Collection $items): RenderedSnapshotGroup
    {
        $renderer = $this->registry->rendererFor($policyType);
        $fallbackRenderer = $this->registry->fallbackRenderer();
        $renderingError = null;
        $subjectDescriptor = $this->subjectDescriptor($policyType);
        $technicalPayload = $this->technicalPayload($items) + ['subject_descriptor' => $subjectDescriptor];

        try {
            $renderedItems = $items
                ->map(fn (BaselineSnapshotItem $item): RenderedSnapshotItem => $renderer->render($item))
                ->all();
        } catch (Throwable) {
            $renderedItems = $items
                ->map(fn (BaselineSnapshotItem $item): RenderedSnapshotItem => $fallbackRenderer->render($item))
                ->all();

            $renderingError = 'Structured rendering failed for this governed subject family. Fallback metadata is shown instead.';
        }

        /** @var array<int, RenderedSnapshotItem> $renderedItems */
        $groupFidelity = FidelityState::aggregate(array_map(
            static fn (RenderedSnapshotItem $item): FidelityState => $item->fidelity,
            $renderedItems,
        ));

        $gapSummary = GapSummary::merge(array_map(
            static fn (RenderedSnapshotItem $item): GapSummary => $item->gapSummary,
            $renderedItems,
        ));

        $capturedAt = collect($renderedItems)
            ->pluck('observedAt')
            ->filter(static fn (mixed $value): bool => is_string($value) && trim($value) !== '')
            ->sortDesc()
            ->first();

        $coverageHint = $groupFidelity->coverageHint();

        if ($coverageHint === null && $gapSummary->messages !== []) {
            $coverageHint = $gapSummary->messages[0];
        }

        return new RenderedSnapshotGroup(
            policyType: $policyType,
            label: $this->typeLabel($policyType),
            itemCount: $items->count(),
            fidelity: $groupFidelity,
            gapSummary: $gapSummary,
            initiallyCollapsed: true,
            items: $renderedItems,
            renderingError: $renderingError,
            coverageHint: $coverageHint,
            capturedAt: is_string($capturedAt) ? $capturedAt : null,
            technicalPayload: $technicalPayload,
            subjectDescriptor: $subjectDescriptor,
        );
    }

    /**
     * @param  Collection<int, BaselineSnapshotItem>  $items
     * @return array<string, mixed>
     */
    private function technicalPayload(Collection $items): array
    {
        return [
            'items' => $items
                ->map(static fn (BaselineSnapshotItem $item): array => [
                    'snapshot_item_id' => (int) $item->getKey(),
                    'policy_type' => (string) $item->policy_type,
                    'meta_jsonb' => is_array($item->meta_jsonb) ? $item->meta_jsonb : [],
                ])
                ->all(),
        ];
    }

    /**
     * @param  array<string, mixed>  $summary
     */
    private function summaryGapCount(array $summary): int
    {
        return $this->summaryGapSummary($summary)->count;
    }

    /**
     * @param  array<string, mixed>  $summary
     */
    private function fidelitySummary(array $summary): string
    {
        $counts = is_array($summary['fidelity_counts'] ?? null)
            ? $summary['fidelity_counts']
            : [];

        $content = is_numeric($counts['content'] ?? null) ? (int) $counts['content'] : 0;
        $meta = is_numeric($counts['meta'] ?? null) ? (int) $counts['meta'] : 0;

        return sprintf(
            '%s %d, %s %d',
            BadgeCatalog::spec(BadgeDomain::BaselineSnapshotFidelity, FidelityState::Full->value)->label,
            $content,
            BadgeCatalog::spec(BadgeDomain::BaselineSnapshotFidelity, FidelityState::ReferenceOnly->value)->label,
            $meta,
        );
    }

    /**
     * @param  array<string, mixed>  $summary
     */
    private function summaryGapSummary(array $summary): GapSummary
    {
        $gaps = is_array($summary['gaps'] ?? null) ? $summary['gaps'] : [];
        $byReason = is_array($gaps['by_reason'] ?? null) ? $gaps['by_reason'] : [];
        $gapSummary = GapSummary::fromReasonMap($byReason);

        if ($byReason !== [] || ! is_numeric($gaps['count'] ?? null) || (int) $gaps['count'] <= 0) {
            return $gapSummary;
        }

        return new GapSummary(
            count: (int) $gaps['count'],
            messages: ['Coverage gaps need review.'],
        );
    }

    private function gapStatusSpec(int $gapCount): \App\Support\Badges\BadgeSpec
    {
        return BadgeRenderer::spec(
            BadgeDomain::BaselineSnapshotGapStatus,
            $gapCount > 0 ? 'gaps_present' : 'clear',
        );
    }

    /**
     * @return array{label: string, color: string, icon: string, iconColor: string}
     */
    private function currentTruthPresentation(ArtifactTruthEnvelope $truth): array
    {
        return match ($truth->artifactExistence) {
            'historical_only' => [
                'label' => 'Historical trace',
                'color' => 'gray',
                'icon' => 'heroicon-m-clock',
                'iconColor' => 'gray',
            ],
            'created_but_not_usable' => [
                'label' => 'Not compare input',
                'color' => 'warning',
                'icon' => 'heroicon-m-exclamation-triangle',
                'iconColor' => 'warning',
            ],
            default => [
                'label' => 'Current baseline',
                'color' => 'success',
                'icon' => 'heroicon-m-check-badge',
                'iconColor' => 'success',
            ],
        };
    }

    private function typeLabel(string $policyType): string
    {
        return (string) (data_get($this->subjectDescriptor($policyType), 'display_label')
            ?? InventoryPolicyTypeMeta::baselineCompareLabel($policyType)
            ?? InventoryPolicyTypeMeta::label($policyType)
            ?? Str::headline($policyType));
    }

    /**
     * @return array<string, mixed>
     */
    private function subjectDescriptor(string $policyType): array
    {
        static $cache = [];

        if (array_key_exists($policyType, $cache)) {
            return $cache[$policyType];
        }

        $result = app(PlatformSubjectDescriptorNormalizer::class)->fromArray([
            'policy_type' => $policyType,
        ], 'baseline_snapshot');

        return $cache[$policyType] = $result->descriptor->toArray();
    }

    private function formatTimestamp(?string $value): string
    {
        if ($value === null || trim($value) === '') {
            return '—';
        }

        try {
            return Carbon::parse($value)->toDayDateTimeString();
        } catch (Throwable) {
            return $value;
        }
    }
}
