<?php

use App\Models\User;
use App\Uploads\Jobs\VirusScanJob;
use Illuminate\Support\Facades\Bus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

function makeValidArchiveUpload(): UploadedFile
{
    $path = tempnam(sys_get_temp_dir(), 'sb_preload_zip_');
    if ($path === false) {
        throw new \RuntimeException('Unable to allocate temporary zip path.');
    }

    $zip = new \ZipArchive();
    if ($zip->open($path, \ZipArchive::OVERWRITE | \ZipArchive::CREATE) !== true) {
        throw new \RuntimeException('Unable to create temporary zip file.');
    }

    $zip->addFromString('skins/theme/readme.txt', 'safe');
    $zip->addFromString('skins/theme/colors.ini', 'accent=blue');
    $zip->close();

    return new UploadedFile($path, 'pack.zip', 'application/zip', null, true);
}

it('authenticated user can preload image', function () {
    Storage::fake('local');
    $user = User::factory()->create();

    $main = UploadedFile::fake()->image('main.jpg', 1200, 800);

    $response = $this
        ->actingAs($user)
        ->postJson('/api/uploads/preload', [
            'main' => $main,
        ]);

    $response->assertOk()->assertJsonStructure([
        'upload_id',
        'status',
        'expires_at',
    ]);

    $uploadId = $response->json('upload_id');

    $this->assertDatabaseHas('uploads', [
        'id' => $uploadId,
        'user_id' => $user->id,
        'type' => 'image',
        'status' => 'draft',
    ]);

    $this->assertDatabaseHas('upload_files', [
        'upload_id' => $uploadId,
        'type' => 'main',
    ]);

    Storage::disk('local')->assertExists("tmp/drafts/{$uploadId}/meta.json");
    expect(Storage::disk('local')->allFiles("tmp/drafts/{$uploadId}"))->not->toBeEmpty();
});

it('authenticated user can preload archive with screenshot', function () {
    Storage::fake('local');
    $user = User::factory()->create();

    $main = makeValidArchiveUpload();
    $screenshot = UploadedFile::fake()->image('screen1.jpg', 800, 600);

    $response = $this
        ->actingAs($user)
        ->postJson('/api/uploads/preload', [
            'main' => $main,
            'screenshots' => [$screenshot],
        ]);

    $response->assertOk()->assertJsonStructure([
        'upload_id',
        'status',
        'expires_at',
    ]);

    $uploadId = $response->json('upload_id');

    $this->assertDatabaseHas('uploads', [
        'id' => $uploadId,
        'user_id' => $user->id,
        'type' => 'archive',
        'status' => 'draft',
    ]);

    $this->assertDatabaseHas('upload_files', [
        'upload_id' => $uploadId,
        'type' => 'main',
    ]);

    $this->assertDatabaseHas('upload_files', [
        'upload_id' => $uploadId,
        'type' => 'screenshot',
    ]);

    Storage::disk('local')->assertExists("tmp/drafts/{$uploadId}/meta.json");
    expect(Storage::disk('local')->allFiles("tmp/drafts/{$uploadId}"))->not->toBeEmpty();
});

it('guest is rejected', function () {
    Storage::fake('local');

    $main = UploadedFile::fake()->image('main.jpg', 1200, 800);

    $response = $this->postJson('/api/uploads/preload', [
        'main' => $main,
    ]);

    expect(in_array($response->getStatusCode(), [401, 403]))->toBeTrue();
});

it('missing main file fails', function () {
    Storage::fake('local');
    $user = User::factory()->create();

    $response = $this
        ->actingAs($user)
        ->postJson('/api/uploads/preload', []);

    $response->assertStatus(422)->assertJsonValidationErrors(['main']);
});

it('archive without screenshot fails', function () {
    Storage::fake('local');
    $user = User::factory()->create();

    $main = UploadedFile::fake()->create('pack.zip', 1024, 'application/zip');

    $response = $this
        ->actingAs($user)
        ->postJson('/api/uploads/preload', [
            'main' => $main,
        ]);

    $response->assertStatus(422)->assertJsonValidationErrors(['screenshots']);
});

it('invalid file type is rejected', function () {
    Storage::fake('local');
    $user = User::factory()->create();

    $main = UploadedFile::fake()->create('notes.txt', 4, 'text/plain');

    $response = $this
        ->actingAs($user)
        ->postJson('/api/uploads/preload', [
            'main' => $main,
        ]);

    $response->assertStatus(422)->assertJsonValidationErrors(['main']);
});

it('preload dispatches VirusScanJob', function () {
    Storage::fake('local');
    Bus::fake();

    $user = User::factory()->create();
    $main = UploadedFile::fake()->image('main.jpg', 1200, 800);

    $response = $this
        ->actingAs($user)
        ->postJson('/api/uploads/preload', [
            'main' => $main,
        ]);

    $response->assertOk()->assertJsonStructure([
        'upload_id',
        'status',
        'expires_at',
    ]);

    $uploadId = $response->json('upload_id');

    Bus::assertDispatched(VirusScanJob::class, function (VirusScanJob $job) use ($uploadId) {
        $reflect = new \ReflectionClass($job);
        $property = $reflect->getProperty('uploadId');
        $property->setAccessible(true);

        return $property->getValue($job) === $uploadId;
    });
});
