<?php

namespace Tests\Unit\Modules\Lemmy\Services;

use App\Modules\Lemmy\Services\ThumbnailUploader;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class ThumbnailUploaderTest extends TestCase
{
    /**
     * @param  int<1, max>  $width
     * @param  int<1, max>  $height
     */
    private function jpeg(int $width, int $height): string
    {
        $image = imagecreatetruecolor($width, $height);
        $colour = imagecolorallocate($image, 120, 90, 60);
        imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $colour === false ? 0 : $colour);

        ob_start();
        imagejpeg($image, null, 90);
        $bytes = (string) ob_get_clean();
        imagedestroy($image);

        return $bytes;
    }

    private function uploader(): ThumbnailUploader
    {
        return new ThumbnailUploader('https://lemmy.world');
    }

    private function fake(string $sourceBody, mixed $uploadResponse, int $uploadStatus = 200): void
    {
        Http::fake([
            'https://cdn.example.com/*' => Http::response($sourceBody, 200, ['Content-Type' => 'image/jpeg']),
            'https://lemmy.world/pictrs/image' => Http::response($uploadResponse, $uploadStatus),
        ]);
    }

    public function test_it_returns_an_instance_hosted_url(): void
    {
        $this->fake($this->jpeg(1200, 800), ['files' => [['file' => 'abc123.jpg', 'delete_token' => 'tok']]]);

        $url = $this->uploader()->upload('https://cdn.example.com/big.jpg', 'token');

        $this->assertSame('https://lemmy.world/pictrs/image/abc123.jpg', $url);
    }

    public function test_it_downscales_a_wide_image_before_upload(): void
    {
        $source = $this->jpeg(2000, 1000);
        $this->fake($source, ['files' => [['file' => 'abc123.jpg']]]);

        $this->uploader()->upload('https://cdn.example.com/big.jpg', 'token');

        Http::assertSent(function (Request $request) use ($source): bool {
            if ($request->url() !== 'https://lemmy.world/pictrs/image') {
                return false;
            }

            $uploaded = $this->multipartFileContents($request);
            $size = getimagesizefromstring($uploaded);

            return $size !== false
                && $size[0] === 600
                && $size[1] === 300
                && $uploaded !== $source
                && strlen($uploaded) < strlen($source);
        });
    }

    public function test_it_uploads_a_small_image_untouched(): void
    {
        $source = $this->jpeg(320, 240);
        $this->fake($source, ['files' => [['file' => 'abc123.jpg']]]);

        $this->uploader()->upload('https://cdn.example.com/small.jpg', 'token');

        Http::assertSent(function (Request $request) use ($source): bool {
            return $request->url() !== 'https://lemmy.world/pictrs/image'
                || $this->multipartFileContents($request) === $source;
        });
    }

    public function test_it_sends_the_token_as_a_bearer_header(): void
    {
        $this->fake($this->jpeg(800, 600), ['files' => [['file' => 'abc123.jpg']]]);

        $this->uploader()->upload('https://cdn.example.com/big.jpg', 'secret-token');

        Http::assertSent(fn (Request $request): bool => $request->url() !== 'https://lemmy.world/pictrs/image'
            || $request->header('Authorization')[0] === 'Bearer secret-token');
    }

    public function test_it_returns_null_for_a_null_source(): void
    {
        Http::fake();

        $this->assertNull($this->uploader()->upload(null, 'token'));

        Http::assertNothingSent();
    }

    public function test_it_returns_null_for_an_empty_source(): void
    {
        Http::fake();

        $this->assertNull($this->uploader()->upload('', 'token'));

        Http::assertNothingSent();
    }

    public function test_it_returns_null_when_the_download_fails(): void
    {
        Http::fake(['https://cdn.example.com/*' => Http::response('', 404)]);

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/gone.jpg', 'token'));
    }

    public function test_it_returns_null_when_the_source_is_not_an_image(): void
    {
        $this->fake('<html>not an image</html>', ['files' => [['file' => 'abc123.jpg']]]);

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/page.html', 'token'));
    }

    public function test_it_returns_null_when_the_upload_fails(): void
    {
        $this->fake($this->jpeg(1200, 800), ['error' => 'nope'], 500);

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/big.jpg', 'token'));
    }

    public function test_it_returns_null_when_the_upload_response_has_no_file(): void
    {
        $this->fake($this->jpeg(1200, 800), ['files' => []]);

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/big.jpg', 'token'));
    }

    public function test_it_returns_null_when_the_source_exceeds_the_size_cap(): void
    {
        $this->fake(str_repeat('x', 10_485_761), ['files' => [['file' => 'abc123.jpg']]]);

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/huge.jpg', 'token'));
    }

    public function test_it_returns_null_when_the_source_exceeds_the_pixel_cap(): void
    {
        // A small file can still decode to a huge bitmap; this is the guard that protects the worker.
        $this->fake($this->jpeg(9000, 6000), ['files' => [['file' => 'abc123.jpg']]]);

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/huge-dimensions.jpg', 'token'));
    }

    public function test_it_returns_null_when_the_download_throws(): void
    {
        Http::fake(fn () => throw new \RuntimeException('connection refused'));

        $this->assertNull($this->uploader()->upload('https://cdn.example.com/big.jpg', 'token'));
    }

    private function multipartFileContents(Request $request): string
    {
        foreach ($request->data() as $part) {
            if (($part['name'] ?? null) === 'images[]') {
                return (string) $part['contents'];
            }
        }

        return '';
    }
}
