<?php

declare(strict_types=1);

namespace App\Services\Baselines\SnapshotRendering;

use App\Models\BaselineSnapshot;
use App\Models\BaselineSnapshotItem;
use App\Support\Inventory\InventoryPolicyTypeMeta;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
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,
                'itemCount' => $group->itemCount,
                'fidelity' => $group->fidelity->value,
                'gapCount' => $group->gapSummary->count,
                'capturedAt' => $group->capturedAt,
                'coverageHint' => $group->coverageHint,
            ],
            $groups,
        );

        $overallGapCount = $this->summaryGapCount($summary);
        $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: $overallGapCount > 0 ? 'Captured with gaps' : 'Complete',
            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  Collection<int, BaselineSnapshotItem>  $items
     */
    private function presentGroup(string $policyType, Collection $items): RenderedSnapshotGroup
    {
        $renderer = $this->registry->rendererFor($policyType);
        $fallbackRenderer = $this->registry->fallbackRenderer();
        $renderingError = null;
        $technicalPayload = $this->technicalPayload($items);

        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 policy type. 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,
        ));

        if ($renderingError !== null) {
            $gapSummary = $gapSummary->withMessage($renderingError);
        }

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

    /**
     * @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
    {
        $gaps = is_array($summary['gaps'] ?? null) ? $summary['gaps'] : [];
        $count = $gaps['count'] ?? 0;

        return is_numeric($count) ? (int) $count : 0;
    }

    /**
     * @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('Content %d, Meta %d', $content, $meta);
    }

    private function typeLabel(string $policyType): string
    {
        return InventoryPolicyTypeMeta::baselineCompareLabel($policyType)
            ?? InventoryPolicyTypeMeta::label($policyType)
            ?? Str::headline($policyType);
    }
}
