<?php

declare(strict_types=1);

namespace App\Tests\Unit\Service\Utils;

use App\Entity\Core\File;
use App\Service\Utils\FileUtils;
use Path\Path;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\FileLocator;

class TestableFileUtils extends FileUtils
{
    public function makePath(string $path): Path
    {
        return parent::makePath($path);
    }

    public function makeFileLocator(array $directories): FileLocator
    {
        return parent::makeFileLocator($directories);
    }
}

class FileUtilsTest extends TestCase
{
    private function getMockFor(string $methodName): TestableFileUtils|MockObject
    {
        return $this
            ->getMockBuilder(TestableFileUtils::class)
            ->setMethodsExcept([$methodName])
            ->getMock();
    }

    public function testGetMimeTypeFromExt()
    {
        $fileUtils = $this->getMockFor('getMimeTypeFromExt');

        $this->assertEquals(
            'image/jpeg',
            $fileUtils->getMimeTypeFromExt('jpg')
        );

        $this->assertEquals(
            'image/png',
            $fileUtils->getMimeTypeFromExt('.png')
        );

        $this->assertEquals(
            null,
            $fileUtils->getMimeTypeFromExt('.unknown')
        );
    }

    public function testGetMimeTypeFromFilename()
    {
        $fileUtils = $this->getMockFor('guessMimeTypeFromFilename');

        $fileUtils->method('getMimeTypeFromExt')->with('png')->willReturn('image/png');

        $this->assertEquals(
            'image/png',
            $fileUtils->guessMimeTypeFromFilename('my_file.png')
        );
    }

    public function testIsImage()
    {
        $fileUtils = $this->getMockFor('isImage');

        // has a registered mimetype
        $file1 = $this->getMockBuilder(File::class)->getMock();
        $file1->method('getMimeType')->willReturn('image/png');

        // has no registered mimetype
        $file2 = $this->getMockBuilder(File::class)->getMock();
        $file2->method('getMimeType')->willReturn(null);
        $file2->method('getName')->willReturn('my_file.png');

        // has no registered mimetype and is not an image
        $file3 = $this->getMockBuilder(File::class)->getMock();
        $file3->method('getMimeType')->willReturn(null);
        $file3->method('getName')->willReturn('my_file.pdf');

        $fileUtils->method('guessMimeTypeFromFilename')->willReturnMap([
            ['my_file.png', 'image/png'],
            ['my_file.pdf', 'document/pdf'],
        ]);

        $this->assertTrue($fileUtils->isImage($file1));
        $this->assertTrue($fileUtils->isImage($file2));
        $this->assertFalse($fileUtils->isImage($file3));
    }

    public function testGetTempFilename()
    {
        $fileUtils = $this->getMockFor('getTempFilename');

        $tempDirPath = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $tempDirPath->expects($this->once())->method('mkdir')->with(511, true);

        $fileUtils->method('makePath')->willReturn($tempDirPath);

        $this->assertMatchesRegularExpression(
            '/^.*\/var\/tmp\/\w{13}\.jpg$/',
            $fileUtils->getTempFilename('jpg')
        );
    }

    public function testGetTempFilenameWithPrefix()
    {
        $fileUtils = $this->getMockFor('getTempFilename');

        $tempDirPath = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $tempDirPath->expects($this->once())->method('mkdir')->with(511, true);

        $fileUtils->method('makePath')->willReturn($tempDirPath);

        $this->assertMatchesRegularExpression(
            '/^.*\/var\/tmp\/foo\w{13}\.jpg$/',
            $fileUtils->getTempFilename('jpg', 'foo')
        );
    }

    public function testRmIfExist()
    {
        $fileUtils = $this->getMockFor('rmIfExist');

        $myPath = '/path/to/something';

        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $fileUtils->method('makePath')->with($myPath)->willReturn($path);

        $path->expects($this->once())->method('remove_p');

        $fileUtils->rmIfExist($myPath);
    }

    public function testGetFileContent()
    {
        $fileUtils = $this->getMockFor('getFileContent');

        $myPath = '/path/to/something';

        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $fileUtils->method('makePath')->with($myPath)->willReturn($path);

        $path->expects($this->once())->method('getContent')->willReturn('my content');

        $this->assertEquals('my content', $fileUtils->getFileContent($myPath));
    }

    public function testLocate()
    {
        $fileUtils = $this->getMockFor('locate');

        $fileLocator = $this->getMockBuilder(FileLocator::class)->disableOriginalConstructor()->getMock();

        $fileLocator
            ->expects($this->once())
            ->method('locate')
            ->with('my_file.pdf', null, false)
            ->willReturn(['/my/dir/my_file.pdf']);

        $fileUtils->method('makeFileLocator')->with(['/my/dir'])->willReturn($fileLocator);

        $this->assertEquals(
            '/my/dir/my_file.pdf',
            $fileUtils->locate(['/my/dir'], 'my_file.pdf')
        );
    }

    public function testLocateNotFound()
    {
        $fileUtils = $this->getMockFor('locate');

        $fileLocator = $this->getMockBuilder(FileLocator::class)->disableOriginalConstructor()->getMock();

        $fileLocator
            ->expects($this->once())
            ->method('locate')
            ->with('my_file.pdf', null, false)
            ->willReturn([]);

        $fileUtils->method('makeFileLocator')->with(['/my/dir'])->willReturn($fileLocator);

        $this->assertEquals(
            null,
            $fileUtils->locate(['/my/dir'], 'my_file.pdf')
        );
    }

    public function testList()
    {
        $fileUtils = $this->getMockFor('list');

        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();

        $fileUtils->method('makePath')->with('/home')->willReturn($path);

        $child1 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $child1->method('__toString')->willReturn('/home/my_file.pdf');

        $child2 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $child2->method('__toString')->willReturn('/home/my_other_file.pdf');

        $path
            ->expects($this->once())
            ->method('glob')
            ->with('*')
            ->willReturn([$child1, $child2]);

        $this->assertEquals(
            ['/home/my_file.pdf', '/home/my_other_file.pdf'],
            $fileUtils->list('/home')
        );
    }

    public function testListWithGlob()
    {
        $fileUtils = $this->getMockFor('list');

        $path = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();

        $fileUtils->method('makePath')->with('/home')->willReturn($path);

        $child1 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $child1->method('__toString')->willReturn('/home/my_file.pdf');

        $child2 = $this->getMockBuilder(Path::class)->disableOriginalConstructor()->getMock();
        $child2->method('__toString')->willReturn('/home/my_other_file.pdf');

        $path
            ->expects($this->once())
            ->method('glob')
            ->with('*.pdf')
            ->willReturn([$child1, $child2]);

        $this->assertEquals(
            ['/home/my_file.pdf', '/home/my_other_file.pdf'],
            $fileUtils->list('/home', '*.pdf')
        );
    }
}
