<?php

declare(strict_types=1);

use App\Console\Commands\RecomputeUserStatsCommand;
use App\Models\Artwork;
use App\Models\ArtworkAward;
use App\Models\ArtworkComment;
use App\Models\ArtworkFavourite;
use App\Models\ArtworkReaction;
use App\Models\User;
use App\Services\ArtworkAwardService;
use App\Services\UserStatsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;

uses(RefreshDatabase::class);

// ─── Helpers ─────────────────────────────────────────────────────────────────

function makeCreator(): User
{
    return User::factory()->create(['is_active' => true]);
}

function makeArtworkFor(User $user): Artwork
{
    return Artwork::factory()->create([
        'user_id'    => $user->id,
        'is_public'  => true,
        'is_approved'=> true,
    ]);
}

function statsRow(int $userId): object
{
    return DB::table('user_statistics')->where('user_id', $userId)->first();
}

// ─── 1. Schema ───────────────────────────────────────────────────────────────

test('user_statistics v2 schema has all expected columns', function () {
    $columns = DB::getSchemaBuilder()->getColumnListing('user_statistics');

    $expected = [
        'user_id',
        'uploads_count',
        'downloads_received_count',
        'artwork_views_received_count',
        'awards_received_count',
        'favorites_received_count',
        'comments_received_count',
        'reactions_received_count',
        'profile_views_count',
        'followers_count',
        'following_count',
        'last_upload_at',
        'last_active_at',
        'created_at',
        'updated_at',
    ];

    foreach ($expected as $col) {
        expect(in_array($col, $columns, true))->toBeTrue("Column '{$col}' is missing from user_statistics");
    }

    // Old column names must NOT exist
    foreach (['uploads', 'downloads', 'pageviews', 'awards', 'profile_views'] as $old) {
        expect(in_array($old, $columns, true))->toBeFalse("Old column '{$old}' still present in user_statistics");
    }
});

// ─── 2. UserStatsService – ensureRow ─────────────────────────────────────────

test('ensureRow creates a stats row if none exists', function () {
    $user = makeCreator();
    DB::table('user_statistics')->where('user_id', $user->id)->delete();

    app(UserStatsService::class)->ensureRow($user->id);

    expect(DB::table('user_statistics')->where('user_id', $user->id)->exists())->toBeTrue();
});

test('ensureRow does not throw if row already exists', function () {
    $user = makeCreator();
    app(UserStatsService::class)->ensureRow($user->id);
    app(UserStatsService::class)->ensureRow($user->id); // second call should not fail

    expect(DB::table('user_statistics')->where('user_id', $user->id)->count())->toBe(1);
});

// ─── 3. UserStatsService – increment / decrement ────────────────────────────

test('incrementUploads increments uploads_count atomically', function () {
    Queue::fake(); // prevent Meilisearch reindex job
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    $svc->incrementUploads($user->id);
    $svc->incrementUploads($user->id, 4);

    expect((int) statsRow($user->id)->uploads_count)->toBe(5);
});

test('decrementUploads does not go below zero', function () {
    Queue::fake();
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    // Ensure row exists with default 0, then try to decrement
    $svc->ensureRow($user->id);
    $svc->decrementUploads($user->id, 10);
    expect((int) statsRow($user->id)->uploads_count)->toBe(0);
});

test('incrementFavoritesReceived increments the counter', function () {
    Queue::fake();
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    $svc->incrementFavoritesReceived($user->id);
    $svc->incrementFavoritesReceived($user->id);

    expect((int) statsRow($user->id)->favorites_received_count)->toBe(2);
});

test('decrementFavoritesReceived does not go below zero', function () {
    Queue::fake();
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    // Ensure row exists with default 0, then try to decrement
    $svc->ensureRow($user->id);
    $svc->decrementFavoritesReceived($user->id);

    expect((int) statsRow($user->id)->favorites_received_count)->toBe(0);
});

test('incrementCommentsReceived and decrementCommentsReceived work symmetrically', function () {
    Queue::fake();
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    $svc->incrementCommentsReceived($user->id);
    $svc->incrementCommentsReceived($user->id);
    $svc->decrementCommentsReceived($user->id);

    expect((int) statsRow($user->id)->comments_received_count)->toBe(1);
});

test('incrementReactionsReceived and decrementReactionsReceived work symmetrically', function () {
    Queue::fake();
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    $svc->incrementReactionsReceived($user->id, 3);
    $svc->decrementReactionsReceived($user->id, 2);

    expect((int) statsRow($user->id)->reactions_received_count)->toBe(1);
});

test('incrementAwardsReceived and decrementAwardsReceived work symmetrically', function () {
    Queue::fake();
    $user = makeCreator();
    $svc = app(UserStatsService::class);

    $svc->incrementAwardsReceived($user->id);
    $svc->decrementAwardsReceived($user->id);

    expect((int) statsRow($user->id)->awards_received_count)->toBe(0);
});

test('incrementProfileViews increments profile_views_count', function () {
    Queue::fake();
    $user = makeCreator();
    app(UserStatsService::class)->incrementProfileViews($user->id, 5);

    expect((int) statsRow($user->id)->profile_views_count)->toBe(5);
});

// ─── 4. Timestamps ───────────────────────────────────────────────────────────

test('setLastUploadAt writes the timestamp', function () {
    Queue::fake();
    $user = makeCreator();
    $ts = now()->subHours(3);

    app(UserStatsService::class)->setLastUploadAt($user->id, $ts);

    $row = statsRow($user->id);
    expect($row->last_upload_at)->not->toBeNull();
});

test('setLastActiveAt writes the timestamp', function () {
    Queue::fake();
    $user = makeCreator();

    app(UserStatsService::class)->setLastActiveAt($user->id);

    expect(statsRow($user->id)->last_active_at)->not->toBeNull();
});

// ─── 5. Observer wiring – Artwork created ────────────────────────────────────

test('creating an artwork increments uploads_count for its owner', function () {
    Queue::fake();
    $creator = makeCreator();
    DB::table('user_statistics')->where('user_id', $creator->id)->delete();

    makeArtworkFor($creator);

    expect((int) statsRow($creator->id)->uploads_count)->toBe(1);
});

test('soft-deleting an artwork decrements uploads_count', function () {
    Queue::fake();
    $creator = makeCreator();
    $artwork = makeArtworkFor($creator);

    $before = (int) statsRow($creator->id)->uploads_count;
    $artwork->delete();

    expect((int) statsRow($creator->id)->uploads_count)->toBe(max(0, $before - 1));
});

// ─── 6. Observer wiring – Favourites ─────────────────────────────────────────

test('adding a favourite increments creator favorites_received_count', function () {
    Queue::fake();
    $creator = makeCreator();
    $liker   = makeCreator();
    $artwork = makeArtworkFor($creator);

    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['favorites_received_count' => 0]);

    ArtworkFavourite::create(['user_id' => $liker->id, 'artwork_id' => $artwork->id]);

    expect((int) statsRow($creator->id)->favorites_received_count)->toBe(1);
});

test('removing a favourite decrements creator favorites_received_count', function () {
    Queue::fake();
    $creator = makeCreator();
    $liker   = makeCreator();
    $artwork = makeArtworkFor($creator);

    $fav = ArtworkFavourite::create(['user_id' => $liker->id, 'artwork_id' => $artwork->id]);
    $after = (int) statsRow($creator->id)->favorites_received_count;

    $fav->delete();

    expect((int) statsRow($creator->id)->favorites_received_count)->toBe(max(0, $after - 1));
});

// ─── 7. Observer wiring – Comments ───────────────────────────────────────────

test('adding a comment increments creator comments_received_count', function () {
    Queue::fake();
    $creator    = makeCreator();
    $commenter  = makeCreator();
    $artwork    = makeArtworkFor($creator);

    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['comments_received_count' => 0]);

    ArtworkComment::create([
        'artwork_id' => $artwork->id,
        'user_id'    => $commenter->id,
        'content'    => 'Nice work!',
        'is_approved'=> true,
    ]);

    expect((int) statsRow($creator->id)->comments_received_count)->toBe(1);
});

test('soft-deleting a comment decrements creator comments_received_count', function () {
    Queue::fake();
    $creator   = makeCreator();
    $commenter = makeCreator();
    $artwork   = makeArtworkFor($creator);

    $comment = ArtworkComment::create([
        'artwork_id' => $artwork->id,
        'user_id'    => $commenter->id,
        'content'    => 'Hi',
        'is_approved'=> true,
    ]);
    $before = (int) statsRow($creator->id)->comments_received_count;

    $comment->delete();

    expect((int) statsRow($creator->id)->comments_received_count)->toBe(max(0, $before - 1));
});

// ─── 8. Observer wiring – Reactions ──────────────────────────────────────────

test('adding a reaction increments creator reactions_received_count', function () {
    Queue::fake();
    $creator  = makeCreator();
    $reactor  = makeCreator();
    $artwork  = makeArtworkFor($creator);

    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['reactions_received_count' => 0]);

    ArtworkReaction::create([
        'artwork_id' => $artwork->id,
        'user_id'    => $reactor->id,
        'reaction'   => 'heart',
    ]);

    expect((int) statsRow($creator->id)->reactions_received_count)->toBe(1);
});

test('removing a reaction decrements creator reactions_received_count', function () {
    Queue::fake();
    $creator  = makeCreator();
    $reactor  = makeCreator();
    $artwork  = makeArtworkFor($creator);

    $reaction = ArtworkReaction::create([
        'artwork_id' => $artwork->id,
        'user_id'    => $reactor->id,
        'reaction'   => 'thumbs_up',
    ]);
    $before = (int) statsRow($creator->id)->reactions_received_count;

    $reaction->delete();

    expect((int) statsRow($creator->id)->reactions_received_count)->toBe(max(0, $before - 1));
});

// ─── 9. Observer wiring – Awards ────────────────────────────────────────────

test('giving an award increments creator awards_received_count', function () {
    Queue::fake();
    $creator  = makeCreator();
    $awarder  = makeCreator();
    $artwork  = makeArtworkFor($creator);

    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['awards_received_count' => 0]);

    $svc = app(ArtworkAwardService::class);
    $svc->award($artwork, $awarder, 'gold');

    expect((int) statsRow($creator->id)->awards_received_count)->toBe(1);
});

test('removing an award decrements creator awards_received_count', function () {
    Queue::fake();
    $creator  = makeCreator();
    $awarder  = makeCreator();
    $artwork  = makeArtworkFor($creator);

    $svc = app(ArtworkAwardService::class);
    $svc->award($artwork, $awarder, 'gold');
    $before = (int) statsRow($creator->id)->awards_received_count;

    $svc->removeAward($artwork, $awarder);

    expect((int) statsRow($creator->id)->awards_received_count)->toBe(max(0, $before - 1));
});

// ─── 10. Recompute – single user ─────────────────────────────────────────────

test('recomputeUser rebuilds counters from source tables', function () {
    Queue::fake();

    $creator  = makeCreator();
    $fanA     = makeCreator();
    $fanB     = makeCreator();

    $art1 = makeArtworkFor($creator);
    $art2 = makeArtworkFor($creator);

    // Add 2 favourites
    ArtworkFavourite::create(['user_id' => $fanA->id, 'artwork_id' => $art1->id]);
    ArtworkFavourite::create(['user_id' => $fanB->id, 'artwork_id' => $art2->id]);

    // Add 1 comment
    ArtworkComment::create([
        'artwork_id' => $art1->id,
        'user_id'    => $fanA->id,
        'content'    => 'Nice',
        'is_approved'=> true,
    ]);

    // Corrupt the stored counters to simulate drift
    DB::table('user_statistics')->where('user_id', $creator->id)->update([
        'uploads_count'           => 99,
        'favorites_received_count'=> 99,
        'comments_received_count' => 99,
    ]);

    // Recompute should restore correct values
    $svc = app(UserStatsService::class);
    $svc->recomputeUser($creator->id);

    $row = statsRow($creator->id);
    expect((int) $row->uploads_count)->toBe(2)
        ->and((int) $row->favorites_received_count)->toBe(2)
        ->and((int) $row->comments_received_count)->toBe(1);
});

test('recomputeUser dry-run does not write to database', function () {
    Queue::fake();

    $creator = makeCreator();
    makeArtworkFor($creator);

    // Corrupt the counter
    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['uploads_count' => 99]);

    $svc = app(UserStatsService::class);
    $result = $svc->recomputeUser($creator->id, dryRun: true);

    // Returned value should be correct
    expect($result['uploads_count'])->toBe(1);

    // Nothing should have been written
    expect((int) statsRow($creator->id)->uploads_count)->toBe(99);
});

// ─── 11. Recompute command ────────────────────────────────────────────────────

test('recompute command dry-run does not write changes', function () {
    Queue::fake();

    $creator = makeCreator();
    makeArtworkFor($creator);

    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['uploads_count' => 99]);

    $this->artisan('skinbase:recompute-user-stats', [
        'user_id'  => $creator->id,
        '--dry-run'=> true,
    ])->assertSuccessful();

    expect((int) statsRow($creator->id)->uploads_count)->toBe(99);
});

test('recompute command live applies correct values', function () {
    Queue::fake();

    $creator = makeCreator();
    makeArtworkFor($creator);
    makeArtworkFor($creator);

    DB::table('user_statistics')->where('user_id', $creator->id)
        ->update(['uploads_count' => 0]);

    $this->artisan('skinbase:recompute-user-stats', [
        'user_id' => $creator->id,
    ])->assertSuccessful();

    expect((int) statsRow($creator->id)->uploads_count)->toBe(2);
});

test('recompute command --all processes all users', function () {
    Queue::fake();

    $userA = makeCreator();
    $userB = makeCreator();
    makeArtworkFor($userA);

    DB::table('user_statistics')
        ->whereIn('user_id', [$userA->id, $userB->id])
        ->update(['uploads_count' => 0]);

    $this->artisan('skinbase:recompute-user-stats', ['--all' => true])
        ->assertSuccessful();

    expect((int) statsRow($userA->id)->uploads_count)->toBe(1)
        ->and((int) statsRow($userB->id)->uploads_count)->toBe(0);
});

// ─── 12. Meilisearch – toSearchableArray ─────────────────────────────────────

test('User toSearchableArray contains v2 stat fields', function () {
    Queue::fake();
    $user = makeCreator();

    // Ensure stats row exists before updating
    app(UserStatsService::class)->ensureRow($user->id);

    DB::table('user_statistics')->where('user_id', $user->id)->update([
        'uploads_count'                => 10,
        'downloads_received_count'     => 20,
        'artwork_views_received_count' => 30,
        'awards_received_count'        => 4,
        'favorites_received_count'     => 5,
        'comments_received_count'      => 6,
        'reactions_received_count'     => 7,
        'followers_count'              => 100,
        'following_count'              => 50,
    ]);

    $user->load('statistics');
    $arr = $user->toSearchableArray();

    expect($arr)->toHaveKey('uploads_count', 10)
        ->and($arr)->toHaveKey('downloads_received_count', 20)
        ->and($arr)->toHaveKey('artwork_views_received_count', 30)
        ->and($arr)->toHaveKey('awards_received_count', 4)
        ->and($arr)->toHaveKey('favorites_received_count', 5)
        ->and($arr)->toHaveKey('comments_received_count', 6)
        ->and($arr)->toHaveKey('reactions_received_count', 7)
        ->and($arr)->toHaveKey('followers_count', 100)
        ->and($arr)->toHaveKey('following_count', 50);

    // Old key must not be present
    expect(array_key_exists('uploads', $arr))->toBeFalse();
});
