<?php

declare(strict_types=1);

use App\Models\Artwork;
use App\Models\User;
use App\Services\ThumbnailService;
use App\Services\Uploads\UploadStorageService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

uses(RefreshDatabase::class);

beforeEach(function () {
    Storage::fake('s3');

    $suffix = Str::lower(Str::random(10));
    $this->localOriginalsRoot = storage_path('framework/testing/square-command-originals-' . $suffix);

    File::deleteDirectory($this->localOriginalsRoot);
    File::ensureDirectoryExists($this->localOriginalsRoot);

    config()->set('uploads.local_originals_root', $this->localOriginalsRoot);
    config()->set('uploads.object_storage.disk', 's3');
    config()->set('uploads.object_storage.prefix', 'artworks');
});

afterEach(function () {
    File::deleteDirectory($this->localOriginalsRoot);
});

function generateSqCommandImage(string $root, string $filename = 'source.jpg'): string
{
    $path = $root . DIRECTORY_SEPARATOR . $filename;
    $image = imagecreatetruecolor(1200, 800);
    $background = imagecolorallocate($image, 18, 22, 29);
    $subject = imagecolorallocate($image, 245, 180, 110);
    imagefilledrectangle($image, 0, 0, 1200, 800, $background);
    imagefilledellipse($image, 280, 340, 360, 380, $subject);
    imagejpeg($image, $path, 90);
    imagedestroy($image);

    return $path;
}

function generateSqCommandWebp(string $root, string $filename = 'source.webp'): string
{
    $path = $root . DIRECTORY_SEPARATOR . $filename;
    $image = imagecreatetruecolor(1400, 900);
    $background = imagecolorallocate($image, 16, 21, 28);
    $subject = imagecolorallocate($image, 242, 194, 94);
    imagefilledrectangle($image, 0, 0, 1400, 900, $background);
    imagefilledellipse($image, 360, 420, 420, 360, $subject);
    imagewebp($image, $path, 90);
    imagedestroy($image);

    return $path;
}

function seedArtworkWithOriginal(string $localOriginalsRoot): Artwork
{
    $user = User::factory()->create();
    $artwork = Artwork::factory()->for($user)->unpublished()->create();
    $source = generateSqCommandImage($localOriginalsRoot, 'seed.jpg');
    $hash = hash_file('sha256', $source);
    $storage = app(UploadStorageService::class);
    $localTarget = $storage->localOriginalPath($hash, $hash . '.jpg');
    File::ensureDirectoryExists(dirname($localTarget));
    File::copy($source, $localTarget);

    $artwork->update([
        'hash' => $hash,
        'file_ext' => 'jpg',
        'thumb_ext' => 'webp',
        'width' => 1200,
        'height' => 800,
    ]);

    DB::table('artwork_files')->insert([
        'artwork_id' => $artwork->id,
        'variant' => 'orig_image',
        'path' => "artworks/original/" . substr($hash, 0, 2) . "/" . substr($hash, 2, 2) . "/{$hash}.jpg",
        'mime' => 'image/jpeg',
        'size' => (int) filesize($localTarget),
    ]);

    return $artwork->fresh();
}

it('generates missing square thumbnails from the best available source', function () {
    $artwork = seedArtworkWithOriginal($this->localOriginalsRoot);

    $code = Artisan::call('artworks:generate-missing-sq-thumbs');

    expect($code)->toBe(0);
    expect(Artisan::output())->toContain('generated=1');

    $sqPath = "artworks/sq/" . substr($artwork->hash, 0, 2) . "/" . substr($artwork->hash, 2, 2) . "/{$artwork->hash}.webp";
    Storage::disk('s3')->assertExists($sqPath);

    expect(DB::table('artwork_files')->where('artwork_id', $artwork->id)->where('variant', 'sq')->exists())->toBeTrue();

    $sqSize = getimagesizefromstring(Storage::disk('s3')->get($sqPath));
    expect($sqSize[0] ?? null)->toBe(512)
        ->and($sqSize[1] ?? null)->toBe(512);
});

it('supports dry run without writing sq thumbnails', function () {
    $artwork = seedArtworkWithOriginal($this->localOriginalsRoot);

    $code = Artisan::call('artworks:generate-missing-sq-thumbs', ['--dry-run' => true]);

    expect($code)->toBe(0);
    expect(Artisan::output())->toContain('planned=1');

    $sqPath = "artworks/sq/" . substr($artwork->hash, 0, 2) . "/" . substr($artwork->hash, 2, 2) . "/{$artwork->hash}.webp";
    Storage::disk('s3')->assertMissing($sqPath);
    expect(DB::table('artwork_files')->where('artwork_id', $artwork->id)->where('variant', 'sq')->exists())->toBeFalse();
});

it('forces regeneration when an sq row already exists', function () {
    $artwork = seedArtworkWithOriginal($this->localOriginalsRoot);

    DB::table('artwork_files')->insert([
        'artwork_id' => $artwork->id,
        'variant' => 'sq',
        'path' => "artworks/sq/" . substr($artwork->hash, 0, 2) . "/" . substr($artwork->hash, 2, 2) . "/{$artwork->hash}.webp",
        'mime' => 'image/webp',
        'size' => 0,
    ]);

    $code = Artisan::call('artworks:generate-missing-sq-thumbs', ['--force' => true]);

    expect($code)->toBe(0);
    expect(Artisan::output())->toContain('generated=1');

    $sqPath = "artworks/sq/" . substr($artwork->hash, 0, 2) . "/" . substr($artwork->hash, 2, 2) . "/{$artwork->hash}.webp";
    Storage::disk('s3')->assertExists($sqPath);
});

it('purges the canonical sq url after regeneration when Cloudflare is configured', function () {
    Http::fake([
        'https://api.cloudflare.com/client/v4/zones/test-zone/purge_cache' => Http::response(['success' => true], 200),
    ]);

    config()->set('cdn.files_url', 'https://cdn.skinbase.org');
    config()->set('cdn.cloudflare.zone_id', 'test-zone');
    config()->set('cdn.cloudflare.api_token', 'test-token');

    $artwork = seedArtworkWithOriginal($this->localOriginalsRoot);

    $code = Artisan::call('artworks:generate-missing-sq-thumbs', ['--id' => $artwork->id]);

    expect($code)->toBe(0);

    Http::assertSent(function ($request) use ($artwork): bool {
        return $request->url() === 'https://api.cloudflare.com/client/v4/zones/test-zone/purge_cache'
            && $request['files'] === [
                'https://cdn.skinbase.org/artworks/sq/' . substr($artwork->hash, 0, 2) . '/' . substr($artwork->hash, 2, 2) . '/' . $artwork->hash . '.webp',
            ];
    });
});

it('falls back to canonical CDN derivatives for legacy artworks without artwork_files rows', function () {
    $cdnRoot = $this->localOriginalsRoot . DIRECTORY_SEPARATOR . 'fake-cdn';
    File::ensureDirectoryExists($cdnRoot);
    config()->set('cdn.files_url', 'file:///' . str_replace(DIRECTORY_SEPARATOR, '/', $cdnRoot));

    $user = User::factory()->create();
    $hash = '6183c98975512ee6bff4657043067953a33769c7';
    $artwork = Artwork::factory()->for($user)->unpublished()->create([
        'hash' => $hash,
        'file_ext' => 'jpg',
        'thumb_ext' => 'webp',
        'file_path' => 'legacy/uploads/IMG_20210727_090534.jpg',
        'width' => 4624,
        'height' => 2080,
    ]);

    $xlUrl = ThumbnailService::fromHash($hash, 'webp', 'xl');
    $webpSource = generateSqCommandWebp($this->localOriginalsRoot, 'legacy-xl.webp');
    $xlPath = $cdnRoot . DIRECTORY_SEPARATOR . 'artworks' . DIRECTORY_SEPARATOR . 'xl' . DIRECTORY_SEPARATOR . substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2, 2) . DIRECTORY_SEPARATOR . $hash . '.webp';
    File::ensureDirectoryExists(dirname($xlPath));
    File::copy($webpSource, $xlPath);

    expect($xlUrl)->toContain('/artworks/xl/' . substr($hash, 0, 2) . '/' . substr($hash, 2, 2) . '/' . $hash . '.webp');

    $code = Artisan::call('artworks:generate-missing-sq-thumbs', ['--id' => $artwork->id]);

    expect($code)->toBe(0);
    expect(Artisan::output())->toContain('generated=1');

    $sqPath = "artworks/sq/" . substr($hash, 0, 2) . "/" . substr($hash, 2, 2) . "/{$hash}.webp";
    Storage::disk('s3')->assertExists($sqPath);
    expect(DB::table('artwork_files')->where('artwork_id', $artwork->id)->where('variant', 'sq')->exists())->toBeTrue();
});