<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\Artwork;
use App\Models\ArtworkAiAssist;
use App\Services\Studio\StudioAiAssistService;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

final class GenerateArtworkAiSuggestionsCommand extends Command
{
    protected $signature = 'artworks:ai-suggest
                            {artwork_id? : Generate suggestions for a single artwork}
                            {--after-id=0 : Skip artworks with ID <= this value}
                            {--limit= : Stop after processing this many artworks}
                            {--chunk=50 : Database chunk size}
                            {--provider= : Override tag suggestion provider (lm_studio|together)}
                            {--force : Regenerate even when suggestions already exist}
                            {--queue : Queue generation instead of running inline}
                            {--skip-existing : Skip artworks that already have stored tag suggestions}';

    protected $description = 'Generate and store studio AI suggestions for artworks, including 10-15 suggested tags from the md thumbnail';

    public function handle(StudioAiAssistService $aiAssist): int
    {
        $artworkId = $this->argument('artwork_id');
        $afterId = max(0, (int) $this->option('after-id'));
        $limit = $this->option('limit') !== null ? max(1, (int) $this->option('limit')) : null;
        $chunk = max(1, min(200, (int) $this->option('chunk')));
        $provider = $this->normalizeProviderOption($this->option('provider'));
        $force = (bool) $this->option('force');
        $queue = (bool) $this->option('queue');
        $skipExisting = (bool) $this->option('skip-existing');

        if ($provider === null && $this->option('provider') !== null) {
            $this->error('Invalid provider. Supported values: lm_studio, together.');

            return self::FAILURE;
        }

        $processed = 0;
        $generated = 0;
        $skipped = 0;
        $failed = 0;

        $query = Artwork::query()
            ->with('artworkAiAssist')
            ->whereNull('deleted_at')
            ->whereNotNull('hash')
            ->orderBy('id');

        if ($artworkId !== null) {
            $query->where('id', (int) $artworkId);
        } else {
            $query->where('id', '>', $afterId);
        }

        $query->chunkById($chunk, function ($artworks) use (&$processed, &$generated, &$skipped, &$failed, $limit, $skipExisting, $force, $queue, $provider, $aiAssist) {
            foreach ($artworks as $artwork) {
                if ($limit !== null && $processed >= $limit) {
                    return false;
                }

                $processed++;

                $assist = $artwork->artworkAiAssist;
                $hasStoredTags = $assist instanceof ArtworkAiAssist
                    && is_array($assist->tag_suggestions_json)
                    && $assist->tag_suggestions_json !== [];

                if ($skipExisting && $hasStoredTags && ! $force) {
                    $this->line("[#{$artwork->id}] skip existing suggestions");
                    $skipped++;
                    continue;
                }

                $this->line("[#{$artwork->id}] {$artwork->title}");

                try {
                    if ($queue) {
                        $result = $aiAssist->queueAnalysis($artwork, $force, 'tags', $provider);
                        $this->line("  queued ({$result->status})");
                    } else {
                        $result = $aiAssist->analyze($artwork, $force, 'tags', $provider);
                        $this->line("  {$result->status}");
                        $this->renderInlineResult($result);
                    }

                    if ($result->status === ArtworkAiAssist::STATUS_FAILED) {
                        $failed++;
                        continue;
                    }

                    $generated++;
                } catch (\Throwable $exception) {
                    $this->error('  failed: ' . $exception->getMessage());
                    $failed++;
                }
            }

            return null;
        });

        $this->newLine();
        $this->info("Done. processed={$processed} generated={$generated} skipped={$skipped} failed={$failed}");

        return $failed > 0 ? self::FAILURE : self::SUCCESS;
    }

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

        return match (strtolower(trim((string) $value))) {
            'lm_studio', 'lm-studio', 'local', 'home' => 'lm_studio',
            'together', 'together_ai' => 'together',
            default => null,
        };
    }

    private function renderInlineResult(ArtworkAiAssist $assist): void
    {
        if ($assist->status === ArtworkAiAssist::STATUS_FAILED) {
            if (is_string($assist->error_message) && $assist->error_message !== '') {
                $this->error('  error: ' . $assist->error_message);
            }

            return;
        }

        if ($assist->status !== ArtworkAiAssist::STATUS_READY) {
            return;
        }

        $request = is_array($assist->raw_response_json) ? (array) ($assist->raw_response_json['request'] ?? []) : [];
        $tagGeneration = is_array($assist->raw_response_json) ? (array) ($assist->raw_response_json['tag_generation'] ?? []) : [];
        $categorySuggestions = is_array($assist->category_suggestions_json) ? $assist->category_suggestions_json : [];

        $provider = $tagGeneration['provider'] ?? $request['provider'] ?? config('vision.tag_suggestions.provider');
        if (is_string($provider) && $provider !== '') {
            $this->line('  provider: ' . $provider);
        }

        $titles = collect((array) $assist->title_suggestions_json)
            ->pluck('text')
            ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '')
            ->take(3)
            ->values()
            ->all();

        if ($titles !== []) {
            $this->line('  titles: ' . implode(' | ', $titles));
        }

        $tags = collect((array) $assist->tag_suggestions_json)
            ->pluck('tag')
            ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '')
            ->take(12)
            ->values()
            ->all();

        if ($tags !== []) {
            $this->line('  tags: ' . implode(', ', $tags));
        }

        $contentType = $categorySuggestions['content_type']['value'] ?? null;
        $category = $categorySuggestions['category']['value'] ?? null;

        if (is_string($contentType) && $contentType !== '') {
            $line = '  content type: ' . $contentType;
            if (is_string($category) && $category !== '') {
                $line .= ' | category: ' . $category;
            }
            $this->line($line);
        } elseif (is_string($category) && $category !== '') {
            $this->line('  category: ' . $category);
        }

        $description = collect((array) $assist->description_suggestions_json)
            ->pluck('text')
            ->first(fn (mixed $value): bool => is_string($value) && trim($value) !== '');

        if (is_string($description) && $description !== '') {
            $this->line('  description: ' . Str::limit($description, 140, '...'));
        }
    }
}