<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\Artwork;
use Illuminate\Console\Command;
use Meilisearch\Client as MeilisearchClient;

final class InspectArtworkSearchIndexCommand extends Command
{
    protected $signature = 'artworks:search-inspect
                            {artwork_id? : The artwork ID to inspect}
                            {--index= : Override the Meilisearch index name}
                            {--generated-only : Only print the locally generated search document}
                            {--live-only : Only print the live document fetched from Meilisearch}
                            {--json : Print the inspection payload as raw JSON}';

    protected $description = 'Inspect the generated Scout payload and live Meilisearch document for a single artwork.';

    public function handle(MeilisearchClient $client): int
    {
        if ($this->option('generated-only') && $this->option('live-only')) {
            $this->error('Use either --generated-only or --live-only, not both together.');

            return self::FAILURE;
        }

        $artworkId = $this->resolveArtworkId();

        if ($artworkId === null) {
            $this->error('An artwork ID is required.');

            return self::FAILURE;
        }

        $artwork = Artwork::query()
            ->with(['user', 'group', 'tags', 'categories.contentType', 'stats', 'awardStat'])
            ->find($artworkId);

        if ($artwork === null && ! $this->option('live-only')) {
            $this->error("Artwork #{$artworkId} was not found.");

            return self::FAILURE;
        }

        $indexName = $this->resolveIndexName($artwork);
        $inspection = [
            'artwork_id' => $artworkId,
            'index' => $indexName,
            'queue_runtime' => $this->queueRuntimeSummary(),
            'artwork' => $artwork ? $this->artworkSummary($artwork) : null,
            'generated_document' => null,
            'live_document' => null,
            'documents_match' => null,
            'live_fetch_error' => null,
            'diagnosis' => [],
        ];

        if (! $this->option('live-only') && $artwork !== null) {
            $inspection['generated_document'] = $artwork->toSearchableArray();
        }

        if (! $this->option('generated-only')) {
            try {
                $inspection['live_document'] = $client->index($indexName)->getDocument($artworkId);
            } catch (\Throwable $exception) {
                $inspection['live_fetch_error'] = $exception->getMessage();
            }
        }

        if (is_array($inspection['generated_document']) && is_array($inspection['live_document'])) {
            $inspection['documents_match'] = $this->normalizeForComparison($inspection['generated_document'])
                === $this->normalizeForComparison($inspection['live_document']);
        }

        $inspection['diagnosis'] = $this->buildDiagnosis($artwork, $inspection);

        $this->renderInspection($inspection);

        if ($inspection['generated_document'] === null && $inspection['live_document'] === null) {
            return self::FAILURE;
        }

        return self::SUCCESS;
    }

    private function resolveArtworkId(): ?int
    {
        $argument = $this->argument('artwork_id');

        if ($argument !== null && $argument !== '') {
            return max(1, (int) $argument);
        }

        if (! $this->input->isInteractive()) {
            return null;
        }

        $answer = $this->ask('Artwork ID');

        if ($answer === null || trim($answer) === '') {
            return null;
        }

        return max(1, (int) $answer);
    }

    private function resolveIndexName(?Artwork $artwork): string
    {
        $override = trim((string) $this->option('index'));

        if ($override !== '') {
            return $override;
        }

        if ($artwork !== null) {
            return $artwork->searchableAs();
        }

        return (string) config('scout.prefix', '') . 'artworks';
    }

    /**
     * @return array<string, mixed>
     */
    private function artworkSummary(Artwork $artwork): array
    {
        return [
            'id' => (int) $artwork->id,
            'title' => (string) ($artwork->title ?? ''),
            'slug' => (string) ($artwork->slug ?? ''),
            'is_public' => (bool) $artwork->is_public,
            'is_approved' => (bool) $artwork->is_approved,
            'published_at' => $artwork->published_at?->toIso8601String(),
            'should_be_indexed' => (bool) ($artwork->is_public && $artwork->is_approved && $artwork->published_at !== null),
            'searchable_index' => $artwork->searchableAs(),
        ];
    }

    /**
     * @return array<string, string>
     */
    private function queueRuntimeSummary(): array
    {
        return [
            'queue_default_connection' => (string) config('queue.default', 'sync'),
            'scout_queue_connection' => (string) config('scout.queue.connection', (string) config('queue.default', 'sync')),
            'scout_queue_name' => (string) config('scout.queue.queue', 'default'),
        ];
    }

    /**
     * @param  array<string, mixed>  $inspection
     * @return list<string>
     */
    private function buildDiagnosis(?Artwork $artwork, array $inspection): array
    {
        $messages = [];
        $queueDefault = (string) data_get($inspection, 'queue_runtime.queue_default_connection', 'sync');
        $scoutQueueConnection = (string) data_get($inspection, 'queue_runtime.scout_queue_connection', $queueDefault);
        $scoutQueueName = (string) data_get($inspection, 'queue_runtime.scout_queue_name', 'default');

        if ($artwork === null) {
            $messages[] = 'Artwork row was not found locally, so only a direct live-index check was possible.';

            return $messages;
        }

        $shouldBeIndexed = (bool) ($artwork->is_public && $artwork->is_approved && $artwork->published_at !== null);

        if (! $shouldBeIndexed) {
            $messages[] = 'This artwork should not exist in Meilisearch right now because it is not simultaneously public, approved, and published.';
        }

        if (is_string($inspection['live_fetch_error'] ?? null) && str_contains(strtolower((string) $inspection['live_fetch_error']), 'not found')) {
            $messages[] = 'The live Meilisearch document is missing from the inspected index.';

            if ($shouldBeIndexed) {
                $messages[] = 'That usually means one of three things: the artwork has not been indexed yet, the Scout sync worker has not processed the job, or you are inspecting the wrong index name/prefix.';

                if ($scoutQueueConnection !== $queueDefault) {
                    $messages[] = sprintf(
                        'This runtime is using queue.default=%s but Scout sync uses scout.queue.connection=%s on queue=%s. If workers only consume %s, Meilisearch updates will never be processed.',
                        $queueDefault,
                        $scoutQueueConnection,
                        $scoutQueueName,
                        $queueDefault,
                    );

                    if ($scoutQueueConnection === 'database') {
                        $messages[] = sprintf(
                            'In this configuration, artwork indexing writes are likely sitting on the database queue. Either run a worker for that backend, for example: php artisan queue:work database --queue=%s, or align SCOUT_QUEUE_CONNECTION with your main queue backend.',
                            $scoutQueueName,
                        );
                    }
                } else {
                    $messages[] = sprintf(
                        'Scout is configured to use queue connection %s and queue name %s. Make sure at least one worker actively consumes that exact queue.',
                        $scoutQueueConnection,
                        $scoutQueueName,
                    );
                }

                $messages[] = 'If this artwork should be searchable now, requeue it with: php artisan artworks:search-reindex-recent or run a full rebuild with: php artisan artworks:search-rebuild';
            }
        }

        if (($inspection['documents_match'] ?? null) === false) {
            $messages[] = 'The local generated document and the live Meilisearch document differ, so the live index is stale or from a different schema/version.';
        }

        if ($messages === []) {
            $messages[] = 'No obvious indexing problem was detected from this inspection output.';
        }

        return $messages;
    }

    /**
     * @param  array<string, mixed>  $inspection
     */
    private function renderInspection(array $inspection): void
    {
        $jsonFlags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES;

        if ((bool) $this->option('json')) {
            $this->line((string) json_encode($inspection, $jsonFlags));

            return;
        }

        $this->info(sprintf(
            'Artwork search inspect — artwork #%d, index %s',
            (int) $inspection['artwork_id'],
            (string) $inspection['index'],
        ));
        $this->newLine();

        if (is_array($inspection['artwork'])) {
            $this->comment('Artwork');
            $this->line((string) json_encode($inspection['artwork'], $jsonFlags));
            $this->newLine();
        }

        if (is_array($inspection['queue_runtime'])) {
            $this->comment('Queue runtime');
            $this->line((string) json_encode($inspection['queue_runtime'], $jsonFlags));
            $this->newLine();
        }

        if ($inspection['documents_match'] !== null) {
            $this->line('Generated/live document match: ' . ($inspection['documents_match'] ? 'yes' : 'no'));
            $this->newLine();
        }

        if (is_array($inspection['diagnosis']) && $inspection['diagnosis'] !== []) {
            $this->comment('Diagnosis');
            foreach ($inspection['diagnosis'] as $message) {
                $this->line('- ' . $message);
            }
            $this->newLine();
        }

        if (is_array($inspection['generated_document'])) {
            $this->comment('Generated search document');
            $this->line((string) json_encode($inspection['generated_document'], $jsonFlags));
            $this->newLine();
        }

        if (is_array($inspection['live_document'])) {
            $this->comment('Live Meilisearch document');
            $this->line((string) json_encode($inspection['live_document'], $jsonFlags));
            $this->newLine();
        }

        if (is_string($inspection['live_fetch_error']) && $inspection['live_fetch_error'] !== '') {
            $this->warn('Live document fetch failed: ' . $inspection['live_fetch_error']);
        }
    }

    /**
     * @param  mixed  $value
     * @return mixed
     */
    private function normalizeForComparison(mixed $value): mixed
    {
        if (! is_array($value)) {
            return $value;
        }

        foreach ($value as $key => $item) {
            $value[$key] = $this->normalizeForComparison($item);
        }

        if (array_is_list($value)) {
            return $value;
        }

        ksort($value);

        return $value;
    }
}