<?php

declare(strict_types=1);

use App\Models\Artwork;
use App\Models\Category;
use App\Models\ContentType;
use App\Models\UploadBatch;
use App\Models\UploadBatchItem;
use App\Models\User;
use App\Jobs\AnalyzeArtworkAiAssistJob;
use App\Jobs\AutoTagArtworkJob;
use App\Jobs\DetectArtworkMaturityJob;
use App\Jobs\GenerateArtworkEmbeddingJob;
use App\Repositories\Uploads\UploadSessionRepository;
use App\Services\Maturity\ArtworkMaturityService;
use App\Services\TagService;
use App\Services\Uploads\UploadQueueService;
use App\Services\Uploads\UploadSessionStatus;
use App\Services\Uploads\UploadTokenService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia;

uses(RefreshDatabase::class);

function uploadQueueArtwork(array $attributes = []): Artwork
{
    return Artwork::withoutEvents(fn () => Artwork::factory()->create($attributes));
}

function uploadQueueCategory(string $typeName = 'Photography', string $categoryName = 'Portraits'): Category
{
    $suffix = Str::lower(Str::random(6));

    $contentType = ContentType::query()->create([
        'name' => $typeName,
        'slug' => Str::slug($typeName) . '-' . $suffix,
        'order' => 1,
        'hide_from_menu' => false,
    ]);

    return Category::query()->create([
        'content_type_id' => $contentType->id,
        'name' => $categoryName,
        'slug' => Str::slug($categoryName) . '-' . $suffix,
        'is_active' => true,
        'sort_order' => 1,
    ]);
}

beforeEach(function () {
    if (DB::connection()->getDriverName() === 'sqlite') {
        DB::connection()->getPdo()->sqliteCreateFunction('GREATEST', function (...$args) {
            return max($args);
        }, -1);
    }

    $this->user = User::factory()->create();
    $this->actingAs($this->user);
});

test('studio upload queue page loads', function () {
    $this->get('/studio/upload-queue')
        ->assertOk()
        ->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Studio/StudioUploadQueue')
            ->where('title', 'Upload Queue')
            ->has('queue.status_options')
            ->has('queue.sort_options'));
});

test('upload queue batch creation creates draft artworks and queue items with defaults', function () {
    $contentType = ContentType::query()->create([
        'name' => 'Photography',
        'slug' => 'photography',
        'order' => 1,
        'hide_from_menu' => false,
    ]);

    $category = Category::query()->create([
        'content_type_id' => $contentType->id,
        'name' => 'Landscapes',
        'slug' => 'landscapes',
        'is_active' => true,
        'sort_order' => 1,
    ]);

    $response = $this->postJson('/api/studio/upload-queue/batches', [
        'name' => 'Spring Set',
        'files' => [
            ['name' => 'forest-light.png'],
            ['name' => 'city-night.webp'],
        ],
        'defaults' => [
            'category_id' => $category->id,
            'tags' => ['forest', 'set'],
            'visibility' => 'unlisted',
        ],
    ]);

    $response->assertCreated()
        ->assertJsonPath('batch.name', 'Spring Set')
        ->assertJsonCount(2, 'items');

    $batch = UploadBatch::query()->firstOrFail();
    expect($batch->total_items)->toBe(2);

    $items = UploadBatchItem::query()->with(['artwork.categories', 'artwork.tags'])->get();
    expect($items)->toHaveCount(2);

    foreach ($items as $item) {
        expect($item->artwork)->not->toBeNull()
            ->and($item->artwork->visibility)->toBe('unlisted')
            ->and($item->artwork->categories->pluck('id')->all())->toBe([$category->id])
            ->and($item->artwork->tags->pluck('slug')->sort()->values()->all())->toBe(['forest', 'set']);
    }
});

test('upload finish updates queue item when batch item id is supplied', function () {
    config()->set('forum_bot_protection.enabled', false);
    config()->set('uploads.queue_derivatives', false);
    config()->set('uploads.storage_root', storage_path('framework/testing/uploads'));

    Queue::fake();
    File::deleteDirectory((string) config('uploads.storage_root'));

    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Queue batch',
        'status' => 'uploading',
        'total_items' => 1,
    ]);

    $artwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'is_public' => false,
        'is_approved' => false,
        'published_at' => null,
        'artwork_status' => 'draft',
    ]);

    $item = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $artwork->id,
        'original_filename' => 'queue-test.png',
    ]);

    $sessionId = (string) Str::uuid();
    $tmpPath = storage_path('framework/testing/uploads/tmp/' . $sessionId . '.png');
    $sourceImage = base_path('public/favicon/favicon-96x96.png');
    File::ensureDirectoryExists(dirname($tmpPath));
    File::copy($sourceImage, $tmpPath);

    app(UploadSessionRepository::class)->create(
        $sessionId,
        $this->user->id,
        $tmpPath,
        UploadSessionStatus::TMP,
        '127.0.0.1'
    );

    $token = app(UploadTokenService::class)->generate($sessionId, $this->user->id);

    $this->withHeader('X-Upload-Token', $token)
        ->postJson('/api/uploads/finish', [
            'session_id' => $sessionId,
            'artwork_id' => $artwork->id,
            'batch_item_id' => $item->id,
            'file_name' => 'queue-test.png',
        ])
        ->assertOk()
        ->assertJsonPath('artwork_id', $artwork->id)
        ->assertJsonPath('status', UploadSessionStatus::PROCESSED);

    $item->refresh();

    expect($item->status)->toBe('processing')
        ->and($item->processing_stage)->toBe('maturity_check');
});

test('upload queue bulk publish only publishes ready items', function () {
    $contentType = ContentType::query()->create([
        'name' => 'Photography',
        'slug' => 'photography',
        'order' => 1,
        'hide_from_menu' => false,
    ]);

    $category = Category::query()->create([
        'content_type_id' => $contentType->id,
        'name' => 'Portraits',
        'slug' => 'portraits',
        'is_active' => true,
        'sort_order' => 1,
    ]);

    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Publish batch',
        'status' => 'processing',
        'total_items' => 2,
    ]);

    $readyArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Ready artwork',
        'file_name' => 'ready.webp',
        'file_path' => 'artworks/test/ready.webp',
        'hash' => str_repeat('a', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'visibility' => 'public',
        'is_public' => false,
        'is_approved' => false,
        'artwork_status' => 'draft',
        'published_at' => null,
        'maturity_status' => 'clear',
        'maturity_ai_status' => 'succeeded',
    ]);
    $readyArtwork->categories()->sync([$category->id]);

    $blockedArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Blocked artwork',
        'file_name' => 'blocked.webp',
        'file_path' => 'artworks/test/blocked.webp',
        'hash' => str_repeat('b', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'visibility' => 'public',
        'is_public' => false,
        'is_approved' => false,
        'artwork_status' => 'draft',
        'published_at' => null,
        'maturity_status' => 'suspected',
        'maturity_ai_status' => 'succeeded',
    ]);
    $blockedArtwork->categories()->sync([$category->id]);

    $readyItem = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $readyArtwork->id,
        'original_filename' => 'ready.webp',
    ]);

    $blockedItem = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $blockedArtwork->id,
        'original_filename' => 'blocked.webp',
    ]);

    $this->postJson('/api/studio/upload-queue/bulk', [
        'action' => 'publish',
        'item_ids' => [$readyItem->id, $blockedItem->id],
    ])
        ->assertOk()
        ->assertJsonPath('success', 1)
        ->assertJsonPath('failed', 1);

    $readyArtwork->refresh();
    $blockedArtwork->refresh();

    expect($readyArtwork->artwork_status)->toBe('published')
        ->and($readyArtwork->published_at)->not->toBeNull()
        ->and($blockedArtwork->artwork_status)->toBe('draft')
        ->and($blockedArtwork->published_at)->toBeNull();
});

test('upload queue bulk delete only affects owned drafts', function () {
    $otherUser = User::factory()->create();

    $ownedBatch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Delete batch',
        'status' => 'processing',
        'total_items' => 1,
    ]);

    $ownedArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
    ]);

    $foreignBatch = UploadBatch::query()->create([
        'user_id' => $otherUser->id,
        'name' => 'Foreign batch',
        'status' => 'processing',
        'total_items' => 1,
    ]);

    $foreignArtwork = uploadQueueArtwork([
        'user_id' => $otherUser->id,
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
    ]);

    $ownedItem = UploadBatchItem::query()->create([
        'upload_batch_id' => $ownedBatch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $ownedArtwork->id,
        'original_filename' => 'owned.webp',
    ]);

    $foreignItem = UploadBatchItem::query()->create([
        'upload_batch_id' => $foreignBatch->id,
        'user_id' => $otherUser->id,
        'artwork_id' => $foreignArtwork->id,
        'original_filename' => 'foreign.webp',
    ]);

    $this->postJson('/api/studio/upload-queue/bulk', [
        'action' => 'delete',
        'item_ids' => [$ownedItem->id, $foreignItem->id],
        'confirm' => 'DELETE',
    ])
        ->assertOk()
        ->assertJsonPath('success', 1);

    $ownedItem->refresh();
    $foreignItem->refresh();

    expect($ownedItem->status)->toBe('deleted')
        ->and(Artwork::withTrashed()->find($ownedArtwork->id)?->trashed())->toBeTrue()
        ->and($foreignItem->status)->not->toBe('deleted')
        ->and(Artwork::find($foreignArtwork->id))->not->toBeNull();
});

test('upload queue retry rejects drafts without processed media', function () {
    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Retry batch',
        'status' => 'completed_with_errors',
        'total_items' => 1,
    ]);

    $artwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'file_path' => '',
        'hash' => '',
    ]);

    $item = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $artwork->id,
        'original_filename' => 'retry.webp',
        'status' => 'failed',
    ]);

    $this->postJson('/api/studio/upload-queue/items/' . $item->id . '/retry')
        ->assertStatus(422)
        ->assertJsonValidationErrors(['item']);
});

test('upload queue item failure does not break the rest of the batch', function () {
    $response = $this->postJson('/api/studio/upload-queue/batches', [
        'name' => 'Mixed batch',
        'files' => [
            ['name' => 'good.webp'],
            ['name' => 'bad.webp'],
        ],
    ]);

    $batchId = (int) $response->json('batch.id');
    $items = UploadBatchItem::query()->where('upload_batch_id', $batchId)->orderBy('id')->get();

    expect($items)->toHaveCount(2);

    $this->postJson('/api/studio/upload-queue/items/' . $items[1]->id . '/fail', [
        'error_code' => 'invalid_file',
        'error_message' => 'Invalid image payload.',
    ])->assertOk();

    $payload = app(UploadQueueService::class)->listPayload($this->user, ['batch_id' => $batchId]);
    $queueItems = collect($payload['items'])->keyBy('id');

    expect($queueItems)->toHaveCount(2)
        ->and($queueItems[$items[0]->id]['status'])->not->toBe('failed')
        ->and($queueItems[$items[1]->id]['status'])->toBe('failed')
        ->and($queueItems[$items[1]->id]['error_message'])->toBe('Invalid image payload.');
});

test('upload queue processing states update correctly per item', function () {
    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Processing batch',
        'status' => 'uploading',
        'total_items' => 1,
    ]);

    $artwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Processing artwork',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_PENDING,
    ]);

    $item = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $artwork->id,
        'original_filename' => 'processing.webp',
        'status' => 'uploaded',
        'processing_stage' => 'queued',
    ]);

    $queue = app(UploadQueueService::class);

    $queued = $queue->markItemProcessingQueued($item->id);
    expect($queued->status)->toBe('processing')
        ->and($queued->processing_stage)->toBe('thumbnails');

    $artwork->forceFill([
        'file_name' => 'processing.webp',
        'file_path' => 'artworks/test/processing.webp',
        'hash' => str_repeat('c', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
    ])->saveQuietly();

    $processed = $queue->markItemMediaProcessed($item->id);
    expect($processed->status)->toBe('processing')
        ->and($processed->processing_stage)->toBe('maturity_check');
});

test('upload queue publish readiness respects metadata and maturity review rules', function () {
    $category = uploadQueueCategory();

    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Readiness batch',
        'status' => 'processing',
        'total_items' => 4,
    ]);

    $readyArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Ready artwork',
        'file_name' => 'ready.webp',
        'file_path' => 'artworks/test/ready.webp',
        'hash' => str_repeat('d', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_status' => ArtworkMaturityService::STATUS_CLEAR,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_SUCCEEDED,
    ]);
    $readyArtwork->categories()->sync([$category->id]);

    $missingMetadataArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => '',
        'file_name' => 'metadata.webp',
        'file_path' => 'artworks/test/metadata.webp',
        'hash' => str_repeat('e', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_status' => ArtworkMaturityService::STATUS_CLEAR,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_SUCCEEDED,
    ]);
    $missingMetadataArtwork->categories()->sync([$category->id]);

    $reviewArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Review artwork',
        'file_name' => 'review.webp',
        'file_path' => 'artworks/test/review.webp',
        'hash' => str_repeat('f', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_status' => ArtworkMaturityService::STATUS_SUSPECTED,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_SUCCEEDED,
    ]);
    $reviewArtwork->categories()->sync([$category->id]);

    $processingArtwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Processing artwork',
        'file_name' => 'pending',
        'file_path' => '',
        'hash' => '',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_status' => ArtworkMaturityService::STATUS_CLEAR,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_PENDING,
    ]);
    $processingArtwork->categories()->sync([$category->id]);

    $items = collect([
        [$readyArtwork, 'ready.webp'],
        [$missingMetadataArtwork, 'metadata.webp'],
        [$reviewArtwork, 'review.webp'],
        [$processingArtwork, 'processing.webp'],
    ])->map(function (array $entry) use ($batch) {
        [$artwork, $filename] = $entry;

        return UploadBatchItem::query()->create([
            'upload_batch_id' => $batch->id,
            'user_id' => $this->user->id,
            'artwork_id' => $artwork->id,
            'original_filename' => $filename,
            'status' => 'processing',
            'processing_stage' => 'maturity_check',
        ]);
    });

    $payload = app(UploadQueueService::class)->listPayload($this->user, ['batch_id' => $batch->id]);
    $byFilename = collect($payload['items'])->keyBy('original_filename');

    expect($byFilename['ready.webp']['status'])->toBe('ready')
        ->and($byFilename['ready.webp']['is_ready_to_publish'])->toBeTrue()
        ->and($byFilename['metadata.webp']['status'])->toBe('needs_metadata')
        ->and($byFilename['metadata.webp']['is_ready_to_publish'])->toBeFalse()
        ->and($byFilename['review.webp']['status'])->toBe('needs_review')
        ->and($byFilename['review.webp']['is_ready_to_publish'])->toBeFalse()
        ->and($byFilename['processing.webp']['status'])->toBe('processing')
        ->and($byFilename['processing.webp']['is_ready_to_publish'])->toBeFalse();
});

test('upload queue retry works for safe failure cases', function () {
    Queue::fake();

    $category = uploadQueueCategory();

    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'Retry safe batch',
        'status' => 'completed_with_errors',
        'total_items' => 1,
    ]);

    $artwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Retry safe artwork',
        'file_name' => 'retry-safe.webp',
        'file_path' => 'artworks/test/retry-safe.webp',
        'hash' => str_repeat('g', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_status' => ArtworkMaturityService::STATUS_CLEAR,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_PENDING,
    ]);
    $artwork->categories()->sync([$category->id]);

    $item = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $artwork->id,
        'original_filename' => 'retry-safe.webp',
        'status' => 'failed',
        'processing_stage' => 'finalized',
        'error_code' => 'vision_timeout',
        'error_message' => 'Vision analysis timed out.',
    ]);

    $this->postJson('/api/studio/upload-queue/items/' . $item->id . '/retry')
        ->assertOk()
        ->assertJsonPath('ok', true);

    $item->refresh();

    expect($item->status)->toBe('processing')
        ->and($item->processing_stage)->toBe('maturity_check')
        ->and($item->error_code)->toBeNull()
        ->and($item->error_message)->toBeNull();

    Queue::assertPushed(AutoTagArtworkJob::class);
    Queue::assertPushed(DetectArtworkMaturityJob::class);
    Queue::assertPushed(GenerateArtworkEmbeddingJob::class);
    Queue::assertPushed(AnalyzeArtworkAiAssistJob::class);
});

test('upload queue AI generation does not overwrite manual metadata silently', function () {
    Queue::fake();

    $category = uploadQueueCategory();

    $batch = UploadBatch::query()->create([
        'user_id' => $this->user->id,
        'name' => 'AI batch',
        'status' => 'completed_with_errors',
        'total_items' => 1,
    ]);

    $artwork = uploadQueueArtwork([
        'user_id' => $this->user->id,
        'title' => 'Manual title',
        'description' => 'Manual description',
        'file_name' => 'manual.webp',
        'file_path' => 'artworks/test/manual.webp',
        'hash' => str_repeat('h', 64),
        'thumb_ext' => 'webp',
        'file_ext' => 'webp',
        'artwork_status' => 'draft',
        'is_public' => false,
        'published_at' => null,
        'maturity_status' => ArtworkMaturityService::STATUS_CLEAR,
        'maturity_ai_status' => ArtworkMaturityService::AI_STATUS_SUCCEEDED,
    ]);
    $artwork->categories()->sync([$category->id]);
    app(TagService::class)->syncStudioTags($artwork, ['manual-tag']);

    $item = UploadBatchItem::query()->create([
        'upload_batch_id' => $batch->id,
        'user_id' => $this->user->id,
        'artwork_id' => $artwork->id,
        'original_filename' => 'manual.webp',
        'status' => 'failed',
        'processing_stage' => 'finalized',
        'error_code' => 'metadata_failed',
        'error_message' => 'AI metadata generation failed.',
    ]);

    $this->postJson('/api/studio/upload-queue/bulk', [
        'action' => 'generate_ai',
        'item_ids' => [$item->id],
    ])
        ->assertOk()
        ->assertJsonPath('success', 1)
        ->assertJsonPath('failed', 0);

    $artwork->refresh();

    expect($artwork->title)->toBe('Manual title')
        ->and($artwork->description)->toBe('Manual description')
        ->and($artwork->categories()->pluck('categories.id')->all())->toBe([$category->id])
        ->and($artwork->tags()->pluck('tags.slug')->all())->toBe(['manual-tag']);

    Queue::assertPushed(AnalyzeArtworkAiAssistJob::class);
});