<?php

declare(strict_types=1);

use App\Application\Media\ShowImage;
use App\Domain\Media\Image;
use App\Domain\Media\ImageRepository;
use App\Domain\Media\ImageVariantRepository;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\FileSize;
use App\Framework\Core\ValueObjects\Hash;
use App\Framework\Exception\FrameworkException;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\MimeType;
use App\Framework\Router\Result\FileResult;
use App\Framework\Ulid\Ulid;

beforeEach(function () {
    // Create test directory and file
    $this->testDir = '/tmp/test_show_image';
    if (! is_dir($this->testDir)) {
        mkdir($this->testDir, 0755, true);
    }

    $this->testImagePath = $this->testDir . '/test-show.jpg';
    file_put_contents($this->testImagePath, 'fake-jpeg-content');

    // Mock repositories
    $this->imageRepository = Mockery::mock(ImageRepository::class);
    $this->imageVariantRepository = Mockery::mock(ImageVariantRepository::class);
    $this->pathProvider = Mockery::mock(PathProvider::class);

    // Create controller
    $this->controller = new ShowImage(
        $this->pathProvider,
        $this->imageRepository,
        $this->imageVariantRepository
    );

    // Test image entity
    $this->testImage = new Image(
        ulid: Ulid::generate(),
        filename: 'test-show.jpg',
        originalFilename: 'original.jpg',
        mimeType: MimeType::fromString('image/jpeg'),
        fileSize: FileSize::fromBytes(strlen('fake-jpeg-content')),
        width: 300,
        height: 400,
        hash: Hash::fromString('test-hash'),
        path: FilePath::create($this->testDir),
        altText: 'Test show image'
    );
});

afterEach(function () {
    if (file_exists($this->testImagePath)) {
        unlink($this->testImagePath);
    }
    if (is_dir($this->testDir)) {
        rmdir($this->testDir);
    }
    Mockery::close();
});

it('can serve existing image file', function () {
    // Arrange
    $filename = 'test-show.jpg';
    $request = Mockery::mock(HttpRequest::class);

    $this->imageRepository
        ->shouldReceive('findByFilename')
        ->with($filename)
        ->andReturn($this->testImage);

    // Act
    $result = $this->controller->__invoke($filename, $request);

    // Assert
    expect($result)->toBeInstanceOf(FileResult::class);
});

it('falls back to image variant repository when image not found', function () {
    // Arrange
    $filename = 'variant-image.jpg';
    $request = Mockery::mock(HttpRequest::class);

    $this->imageRepository
        ->shouldReceive('findByFilename')
        ->with($filename)
        ->andReturn(null);

    $this->imageVariantRepository
        ->shouldReceive('findByFilename')
        ->with($filename)
        ->andReturn($this->testImage);

    // Act
    $result = $this->controller->__invoke($filename, $request);

    // Assert
    expect($result)->toBeInstanceOf(FileResult::class);
});

it('throws exception when image not found in either repository', function () {
    // Arrange
    $filename = 'nonexistent.jpg';
    $request = Mockery::mock(HttpRequest::class);

    $this->imageRepository
        ->shouldReceive('findByFilename')
        ->with($filename)
        ->andReturn(null);

    $this->imageVariantRepository
        ->shouldReceive('findByFilename')
        ->with($filename)
        ->andReturn(null);

    // Act & Assert
    expect(fn () => $this->controller->__invoke($filename, $request))
        ->toThrow(FrameworkException::class, 'Image not found: nonexistent.jpg');
});

it('throws exception when image file does not exist on filesystem', function () {
    // Arrange
    $filename = 'missing-file.jpg';
    $request = Mockery::mock(HttpRequest::class);

    $missingFileImage = new Image(
        ulid: Ulid::generate(),
        filename: 'missing-file.jpg',
        originalFilename: 'original.jpg',
        mimeType: MimeType::fromString('image/jpeg'),
        fileSize: FileSize::fromBytes(100),
        width: 300,
        height: 400,
        hash: Hash::fromString('test-hash'),
        path: FilePath::create('/nonexistent/path'),
        altText: 'Missing file'
    );

    $this->imageRepository
        ->shouldReceive('findByFilename')
        ->with($filename)
        ->andReturn($missingFileImage);

    // Act & Assert
    expect(fn () => $this->controller->__invoke($filename, $request))
        ->toThrow(FrameworkException::class, 'Image file not found on filesystem');
});

it('determines correct MIME type based on file extension', function () {
    // Test JPEG
    $jpegFilename = 'test.jpg';
    $jpegImage = $this->testImage->withFilename($jpegFilename);
    $request = Mockery::mock(HttpRequest::class);

    $this->imageRepository
        ->shouldReceive('findByFilename')
        ->with($jpegFilename)
        ->andReturn($jpegImage);

    $result = $this->controller->__invoke($jpegFilename, $request);
    expect($result)->toBeInstanceOf(FileResult::class);
});

it('handles different image file extensions', function () {
    $extensions = [
        'test.png' => 'image/png',
        'test.gif' => 'image/gif',
        'test.webp' => 'image/webp',
        'test.avif' => 'image/avif',
        'test.unknown' => 'image/jpeg', // fallback
    ];

    foreach ($extensions as $filename => $expectedMime) {
        // Create test file for each extension
        $testPath = $this->testDir . '/' . $filename;
        file_put_contents($testPath, 'fake-content');

        $testImage = $this->testImage->withFilename($filename);
        $request = Mockery::mock(HttpRequest::class);

        $this->imageRepository
            ->shouldReceive('findByFilename')
            ->with($filename)
            ->andReturn($testImage);

        $result = $this->controller->__invoke($filename, $request);
        expect($result)->toBeInstanceOf(FileResult::class);

        // Clean up
        unlink($testPath);
    }
});
