<?php

declare(strict_types=1);

namespace App\Console\Commands;

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

/**
 * Directly write a single artwork into the Meilisearch index, bypassing the queue.
 *
 * Useful when:
 *   - A rebuild was run but the queue worker was not consuming the `search` queue.
 *   - A specific artwork is missing from the live index and you want it visible immediately.
 *   - You need to force-push a corrected document after schema changes.
 *
 * Usage:
 *   php artisan artworks:search-force-index 69810
 *   php artisan artworks:search-force-index          # interactive prompt
 *   php artisan artworks:search-force-index 69810 --dry-run
 *   php artisan artworks:search-force-index 69810 --force  # index even if not public/approved/published
 */
final class ForceIndexArtworkCommand extends Command
{
    protected $signature = 'artworks:search-force-index
                            {artwork_id? : The artwork ID to force-index}
                            {--index= : Override the Meilisearch index name}
                            {--dry-run : Show what would be sent without actually writing}
                            {--force : Index the document even when the artwork is not public/approved/published}
                            {--no-cache-bump : Skip bumping the explore cache version after indexing}';

    protected $description = 'Directly push a single artwork into Meilisearch, bypassing the queue.';

    public function handle(MeilisearchClient $client): int
    {
        $artworkId = $this->resolveArtworkId();

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

        $isDryRun = (bool) $this->option('dry-run');
        $forceIndex = (bool) $this->option('force');

        $this->line(sprintf(
            '%sForce-indexing artwork #%d into Meilisearch%s…',
            $isDryRun ? '[DRY RUN] ' : '',
            $artworkId,
            $forceIndex ? ' (--force: eligibility check bypassed)' : '',
        ));

        // ── 1. Load artwork with all relations required for toSearchableArray() ──
        $artwork = Artwork::query()
            ->with(['user', 'group', 'tags', 'categories.contentType', 'stats', 'awardStat'])
            ->find($artworkId);

        if ($artwork === null) {
            $this->error("Artwork #{$artworkId} was not found in the database.");
            return self::FAILURE;
        }

        $this->comment('Artwork');
        $this->line(sprintf(
            '  id=%d  title="%s"  public=%s  approved=%s  published_at=%s',
            (int) $artwork->id,
            (string) ($artwork->title ?? ''),
            $artwork->is_public ? 'yes' : 'no',
            $artwork->is_approved ? 'yes' : 'no',
            $artwork->published_at?->toIso8601String() ?? 'null',
        ));

        // ── 2. Eligibility check ─────────────────────────────────────────────────
        $shouldBeIndexed = $artwork->is_public && $artwork->is_approved && $artwork->published_at !== null;

        if (! $shouldBeIndexed && ! $forceIndex) {
            $this->warn(sprintf(
                'Artwork #%d is not eligible for the public index (is_public=%s, is_approved=%s, published_at=%s). ' .
                'Use --force to index it anyway, or fix the artwork status first.',
                $artworkId,
                $artwork->is_public ? 'true' : 'false',
                $artwork->is_approved ? 'true' : 'false',
                $artwork->published_at?->toIso8601String() ?? 'null',
            ));
            return self::FAILURE;
        }

        if (! $shouldBeIndexed && $forceIndex) {
            $this->warn('Artwork is not normally eligible but --force was passed; indexing anyway.');
        }

        // ── 3. Build the Meilisearch document ────────────────────────────────────
        $document = $artwork->toSearchableArray();

        $this->comment('Generated document');
        $this->line(json_encode($document, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        $this->newLine();

        // ── 4. Resolve index name ────────────────────────────────────────────────
        $indexName = $this->resolveIndexName($artwork);
        $this->line("Target index: {$indexName}");

        if ($isDryRun) {
            $this->info('[DRY RUN] Document was NOT written to Meilisearch. Remove --dry-run to execute.');
            return self::SUCCESS;
        }

        // ── 5. Write directly to Meilisearch (no queue) ──────────────────────────
        try {
            $taskResult = $client->index($indexName)->addDocuments([$document]);
            $taskUid = $taskResult['taskUid'] ?? $taskResult['uid'] ?? 'n/a';
            $this->info(sprintf(
                'Document written to Meilisearch. Task uid: %s',
                is_scalar($taskUid) ? (string) $taskUid : json_encode($taskUid),
            ));
        } catch (\Throwable $e) {
            $this->error('Meilisearch write failed: ' . $e->getMessage());
            return self::FAILURE;
        }

        // ── 6. Bump explore cache version ────────────────────────────────────────
        if (! $this->option('no-cache-bump')) {
            try {
                $newVersion = ((int) Cache::get('explore.cache.version', 1)) + 1;
                Cache::forever('explore.cache.version', $newVersion);
                $this->line("Explore cache version bumped to {$newVersion}.");
            } catch (\Throwable $e) {
                $this->warn('Could not bump explore cache version: ' . $e->getMessage());
            }
        }

        // ── 7. Summary ────────────────────────────────────────────────────────────
        $this->newLine();
        $this->info(sprintf(
            'Artwork #%d ("%s") has been pushed to index "%s" directly.',
            (int) $artwork->id,
            (string) ($artwork->title ?? ''),
            $indexName,
        ));
        $this->line('The artwork should now appear on browse and search pages.');
        $this->line('If Meilisearch was still processing the task you can verify with:');
        $this->line(sprintf('  php artisan artworks:search-inspect %d', $artworkId));

        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;
        }

        return $artwork->searchableAs();
    }
}
