<?php

declare(strict_types=1);

use App\Jobs\RegenerateUserRecommendationCacheJob;
use App\Models\Artwork;
use App\Models\User;
use App\Models\UserRecommendationCache;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;

uses(RefreshDatabase::class);

it('returns feed from cache with cursor pagination', function () {
    $user = User::factory()->create();

    $artworkA = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(3)]);
    $artworkB = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(2)]);
    $artworkC = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(1)]);

    UserRecommendationCache::query()->create([
        'user_id' => $user->id,
        'algo_version' => (string) config('discovery.algo_version'),
        'cache_version' => (string) config('discovery.cache_version'),
        'recommendations_json' => [
            'items' => [
                ['artwork_id' => $artworkA->id, 'score' => 0.9, 'source' => 'profile'],
                ['artwork_id' => $artworkB->id, 'score' => 0.8, 'source' => 'profile'],
                ['artwork_id' => $artworkC->id, 'score' => 0.7, 'source' => 'profile'],
            ],
        ],
        'generated_at' => now(),
        'expires_at' => now()->addMinutes(30),
    ]);

    $first = $this->actingAs($user)->getJson('/api/v1/feed?limit=2');

    $first->assertOk();
    $first->assertJsonPath('meta.cache_status', 'hit');
    expect(count((array) $first->json('data')))->toBe(2);

    $nextCursor = $first->json('meta.next_cursor');
    expect($nextCursor)->not->toBeNull();

    $second = $this->actingAs($user)->getJson('/api/v1/feed?limit=2&cursor=' . urlencode((string) $nextCursor));

    $second->assertOk();
    expect(count((array) $second->json('data')))->toBe(1);
    expect($second->json('meta.next_cursor'))->toBeNull();
});

it('dispatches async regeneration on cache miss and returns cold start items', function () {
    Queue::fake();

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

    $artworkA = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(3)]);
    $artworkB = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(2)]);

    DB::table('artwork_stats')->insert([
        ['artwork_id' => $artworkA->id, 'views' => 100, 'downloads' => 30, 'favorites' => 10, 'rating_avg' => 0, 'rating_count' => 0],
        ['artwork_id' => $artworkB->id, 'views' => 80, 'downloads' => 10, 'favorites' => 5, 'rating_avg' => 0, 'rating_count' => 0],
    ]);

    $response = $this->actingAs($user)->getJson('/api/v1/feed?limit=10');

    $response->assertOk();
    expect(count((array) $response->json('data')))->toBeGreaterThan(0);
    expect((string) $response->json('meta.cache_status'))->toContain('miss');

    Queue::assertPushed(RegenerateUserRecommendationCacheJob::class, function (RegenerateUserRecommendationCacheJob $job) use ($user): bool {
        return $job->userId === $user->id;
    });
});

it('applies diversity guard to avoid near-duplicates in cold start fallback', function () {
    Queue::fake();

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

    $artworkA = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(3)]);
    $artworkB = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(2)]);
    $artworkC = Artwork::factory()->create(['is_public' => true, 'is_approved' => true, 'published_at' => now()->subMinutes(1)]);

    DB::table('artwork_stats')->insert([
        ['artwork_id' => $artworkA->id, 'views' => 200, 'downloads' => 20, 'favorites' => 5, 'rating_avg' => 0, 'rating_count' => 0],
        ['artwork_id' => $artworkB->id, 'views' => 190, 'downloads' => 18, 'favorites' => 4, 'rating_avg' => 0, 'rating_count' => 0],
        ['artwork_id' => $artworkC->id, 'views' => 180, 'downloads' => 12, 'favorites' => 3, 'rating_avg' => 0, 'rating_count' => 0],
    ]);

    DB::table('artwork_similarities')->insert([
        'artwork_id' => $artworkA->id,
        'similar_artwork_id' => $artworkB->id,
        'model' => 'clip',
        'model_version' => 'v1',
        'algo_version' => (string) config('discovery.algo_version'),
        'rank' => 1,
        'score' => 0.991,
        'generated_at' => now(),
        'created_at' => now(),
        'updated_at' => now(),
    ]);

    $response = $this->actingAs($user)->getJson('/api/v1/feed?limit=10');

    $response->assertOk();

    $ids = collect((array) $response->json('data'))->pluck('id')->all();

    expect(in_array($artworkA->id, $ids, true) && in_array($artworkB->id, $ids, true))->toBeFalse();
    expect(in_array($artworkC->id, $ids, true))->toBeTrue();
});
