<?php

declare(strict_types=1);

namespace Builder\Core\Http\Controllers\Resource;

use Builder\BlockEditor\Blocks\BlockRegistry;
use Builder\Core\Exceptions\ResourceNotFoundException;
use Builder\Core\Exceptions\UnauthorizedAccessException;
use Builder\Core\Foundation\Contracts\BuilderAccessible;
use Builder\Core\Foundation\Definition\Field;
use Builder\Core\Foundation\Middleware\BuilderAuth;
use Builder\Core\Foundation\Registry;
use Builder\Core\Foundation\Resources\ResourceDefinition;
use Builder\Core\Foundation\Revisions\RevisionDataExtractor;
use Builder\Core\Foundation\Revisions\RevisionDiff;
use Builder\Core\Models\Revision;
use Builder\Media\Models\Media;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

final class ResourceRevisionIndexController
{
    public function __construct(
        private readonly Registry $registry,
    ) {}

    public function __invoke(Request $request, string $resource, int|string $id): Response
    {
        $definition = $this->resolveRevisableOrFail($resource);
        $this->authorizeResource($request, $definition, 'edit');

        $model = $definition->getModel();
        $record = $model::findOrFail($id);

        $revisions = Revision::query()
            ->where('revisable_type', $model)
            ->where('revisable_id', $id)
            ->orderByDesc('created_at')
            ->orderByDesc('id')
            ->with('user')
            ->get();

        /** @var array<string, string> $fieldLabels */
        $fieldLabels = collect($definition->getAllFields())
            ->mapWithKeys(fn (Field $field): array => [$field->getName() => $field->getLabel()])
            ->all();

        $allMediaIds = $revisions
            ->flatMap(fn (Revision $r) => RevisionDataExtractor::mediaIds($r->revision_data ?? []))
            ->unique()
            ->values()
            ->all();

        /** @var array<int, string> $mediaNames */
        $mediaNames = [];

        if ($allMediaIds !== [] && class_exists('Builder\Media\Models\Media')) {
            /** @var array<int, string> $mediaNames */
            $mediaNames = Media::query()
                ->whereIn('id', $allMediaIds)
                ->pluck('original_filename', 'id')
                ->all();
        }

        $allBlockKeys = $revisions
            ->flatMap(fn (Revision $r) => RevisionDataExtractor::blockKeys($r->revision_data ?? []))
            ->unique()
            ->values()
            ->all();

        /** @var array<string, string> $blockLabels */
        $blockLabels = [];
        /** @var array<string, array<string, string>> $blockFieldLabels */
        $blockFieldLabels = [];

        if ($allBlockKeys !== [] && class_exists('Builder\BlockEditor\Blocks\BlockRegistry')) {
            /** @var BlockRegistry $blockRegistry */
            $blockRegistry = app('Builder\BlockEditor\Blocks\BlockRegistry');

            foreach ($allBlockKeys as $blockKey) {
                $block = $blockRegistry->resolve($blockKey);

                if ($block !== null) {
                    $blockLabels[$blockKey] = $block::getLabel();

                    $labelMap = [];
                    foreach ($block->fields()->toFields() as $field) {
                        $labelMap[$field->getName()] = $field->getLabel();
                    }

                    $blockFieldLabels[$blockKey] = $labelMap;
                }
            }
        }

        $revisionEntries = [];

        foreach ($revisions as $index => $revision) {
            $next = $revisions->get($index + 1);
            $previousData = $next instanceof Revision ? ($next->revision_data ?? []) : [];

            $revisionEntries[] = [
                'id' => $revision->id,
                'created_at' => $revision->created_at->toIso8601String(),
                'user_name' => $revision->user?->getAttribute('name'),
                'diff' => $revision->diffFrom($previousData),
                'translation_diff' => $revision->translationDiffFrom($previousData),
                'block_granular_diff' => $revision->blockGranularDiffFrom($previousData),
                'translation_block_granular_diff' => $revision->translationBlockGranularDiffFrom($previousData),
                'media_diff' => $revision->mediaDiffFrom($previousData),
                'field_labels' => $fieldLabels,
                'media_names' => $mediaNames,
                'block_labels' => $blockLabels,
                'block_field_labels' => $blockFieldLabels,
                'is_restored' => isset($revision->revision_data[RevisionDiff::KEY_RESTORED_FROM]),
                'restored_from_id' => $revision->revision_data[RevisionDiff::KEY_RESTORED_FROM] ?? null,
            ];
        }

        $prefix = (string) config('builder.prefix', 'admin');

        return Inertia::render('Resource/Revisions', [
            'resource' => [
                'slug' => $definition->getSlug(),
                'label' => $definition->getLabel(),
                'singularLabel' => $definition->getSingularLabel(),
            ],
            'record' => $record->toArray(),
            'record_id' => $id,
            'revisions' => $revisionEntries,
            'back_url' => "/{$prefix}/{$resource}/{$id}/edit",
        ]);
    }

    private function resolveRevisableOrFail(string $resource): ResourceDefinition
    {
        $definition = $this->registry->resolveBySlug($resource);

        if ($definition === null) {
            throw ResourceNotFoundException::forSlug($resource);
        }

        abort_unless($definition->isRevisable(), 404);

        return $definition;
    }

    private function authorizeResource(Request $request, ResourceDefinition $definition, string $action): void
    {
        $permission = $definition->resolvePermissionForAction($action);

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

        /** @var BuilderAccessible $user */
        $user = $request->user(BuilderAuth::guard());

        if (! (method_exists($user, 'hasPermissionTo') && $user->hasPermissionTo($permission))) {
            throw UnauthorizedAccessException::forPermission($permission);
        }
    }
}
