<?php

namespace Velis\Test\Filesystem;

use PHPUnit\Framework\TestCase;
use RuntimeException;
use Velis\Filesystem\Filesystem;

/**
 * @author Jan Małysiak <jan.malysiak@velis.pl>
 */
class FilesystemTest extends TestCase
{
    const FILENAME = 'test.txt';


    /**
     * @var Filesystem
     */
    private $filesystem;


    /**
     * {@inheritDoc}
     */
    protected function setUp(): void
    {
        $this->filesystem = new Filesystem(__DIR__);
    }


    public function testHas()
    {
        $this->assertFalse($this->filesystem->has(self::FILENAME));

        file_put_contents(__DIR__ . '/' . self::FILENAME, 'test');

        $this->assertTrue($this->filesystem->has(self::FILENAME));
    }


    public function testRename()
    {
        $filename = 'testRename.txt';

        file_put_contents(__DIR__ . '/' . $filename, 'test');

        $this->assertTrue($this->filesystem->rename($filename, self::FILENAME));

        $this->assertTrue(file_exists(__DIR__ . '/' . self::FILENAME));
        $this->assertFalse(file_exists(__DIR__  . '/' . $filename));
    }


    public function testWrite()
    {
        $this->expectException(RuntimeException::class);

        $this->assertEquals(4, $this->filesystem->write(self::FILENAME, 'test'));
        $this->assertEquals(6, $this->filesystem->write(self::FILENAME, 'foobar', true));

        $this->filesystem->write(self::FILENAME, 'quack', false);
    }


    public function testWriteCreatesSubdirectories()
    {
        $this->filesystem->write('sub/directory/test.txt', 'test');

        $this->assertTrue(file_exists(__DIR__ . '/sub/directory'));
        $this->assertTrue(is_dir(__DIR__ . '/sub/directory'));
        $this->assertTrue(file_exists(__DIR__ . '/sub/directory/test.txt'));

        unlink(__DIR__ . '/sub/directory/test.txt');
        rmdir(__DIR__ . '/sub/directory');
        rmdir(__DIR__ . '/sub');
    }


    public function testRead()
    {
        $contents = md5(time());

        file_put_contents(__DIR__ . '/' . self::FILENAME, $contents);

        $this->assertEquals($contents, $this->filesystem->read(self::FILENAME));
    }


    public function testReadNotExistingFile()
    {
        $this->expectException(RuntimeException::class);

        $this->filesystem->read('doesNotExist.txt');
    }


    public function testDelete()
    {
        file_put_contents(__DIR__ . '/' . self::FILENAME, 'test');

        $this->filesystem->delete(self::FILENAME);

        $this->assertFalse(file_exists(__DIR__ . '/' . self::FILENAME));
    }


    public function testDeleteDirectory()
    {
        $dirname = uniqid();
        mkdir(__DIR__ . '/' . $dirname);

        $this->filesystem->delete($dirname);

        $this->assertFalse(file_exists(__DIR__ . '/' . $dirname));
    }


    public function testDeleteNotExistingFile()
    {
        $this->expectException(RuntimeException::class);

        $this->filesystem->delete('doesNotExist.txt');
    }


    public function testKeys()
    {
        file_put_contents(__DIR__ . '/' . self::FILENAME, 'test');
        @mkdir(__DIR__ . '/testdir');
        file_put_contents(__DIR__ . '/testdir/test2.txt', 'test');

        foreach ($this->filesystem->keys() as $key) {
            $this->assertTrue(file_exists(__DIR__ . '/'. $key));
        }

        unlink(__DIR__ . '/testdir/test2.txt');
        rmdir(__DIR__ . '/testdir');
    }


    /**
     * @param string $prefix
     * @param array  $expectedResult
     *
     * @dataProvider provideTestDataForListKeysMethod
     */
    public function testListKeys($prefix, $expectedResult)
    {
        file_put_contents(__DIR__ . '/' . self::FILENAME, 'test');
        @mkdir(__DIR__ . '/testdir');
        file_put_contents(__DIR__ . '/testdir/fileInSubdirectory.txt', 'test');

        $actualResult = $this->filesystem->listKeys($prefix);

        static::assertEquals(sort($expectedResult), sort($actualResult));

        unlink(__DIR__ . '/testdir/fileInSubdirectory.txt');
        rmdir(__DIR__ . '/testdir');
    }


    public function provideTestDataForListKeysMethod()
    {
        return [
            ['testdir/', ['testdir/fileInSubdirectory.txt']],
            ['test', ['testdir/fileInSubdirectory.txt', 'testdir', self::FILENAME]],
            ['test.', [self::FILENAME]],
        ];
    }


    public function testListKeysWithNestedDirectories()
    {
        @mkdir(__DIR__ . '/testdir');
        @mkdir(__DIR__ . '/testdir/1');
        @mkdir(__DIR__ . '/testdir/1/2');
        file_put_contents(__DIR__ . '/testdir/1/2/3', 'test');

        $this->assertEquals(['testdir/1/2/3', 'testdir/1/2'], $this->filesystem->listKeys('testdir/1/2'));

        unlink(__DIR__ . '/testdir/1/2/3');
        rmdir(__DIR__ . '/testdir/1/2');
        rmdir(__DIR__ . '/testdir/1');
        rmdir(__DIR__ . '/testdir');
    }


    public function testMtime()
    {
        file_put_contents(__DIR__ . '/'. self::FILENAME, 'test');

        $this->assertEquals(filectime(__DIR__ . '/' . self::FILENAME), $this->filesystem->mtime(self::FILENAME));
    }


    public function testChecksum()
    {
        $content = uniqid();
        file_put_contents(__DIR__ . '/'. self::FILENAME, $content);

        $this->assertEquals(md5($content), $this->filesystem->checksum(self::FILENAME));
    }


    public function testSize()
    {
        file_put_contents(__DIR__ . '/'. self::FILENAME, 'test');

        $this->assertEquals(filesize(__DIR__ . '/' . self::FILENAME), $this->filesystem->size(self::FILENAME));
    }


    public function testMimeType()
    {
        file_put_contents(__DIR__ . '/'. self::FILENAME, 'test');

        $this->assertEquals('text/plain', $this->filesystem->mimeType(self::FILENAME));
    }


    public function testIsDirectory()
    {
        file_put_contents(__DIR__ . '/'. self::FILENAME, 'test');

        $this->assertFalse($this->filesystem->isDirectory(self::FILENAME));

        @mkdir(__DIR__ . '/testdir');

        $this->assertTrue($this->filesystem->isDirectory('testdir'));

        rmdir(__DIR__ . '/testdir');
    }


    public function testUpload()
    {
        $tmpFileName = __DIR__ . '/' . md5(time());
        file_put_contents($tmpFileName, 'test');

        $this->filesystem->upload(self::FILENAME, $tmpFileName);

        $this->assertTrue(file_exists(__DIR__ . '/' . self::FILENAME));
        $this->assertFalse(file_exists($tmpFileName));
    }


    /**
     * @param string $key
     * @param int    $width
     * @param int    $height
     *
     * @dataProvider provideImageFile
     */
    public function testGetImageSize($key, $width, $height)
    {
        $imageSizeArray = $this->filesystem->getImageSize($key);

        $this->assertTrue(is_array($imageSizeArray));
        $this->assertEquals($width, $imageSizeArray[0]);
        $this->assertEquals($height, $imageSizeArray[1]);

        unlink(__DIR__ . '/' . $key);
    }


    /**
     * @return array
     */
    public function provideImageFile()
    {
        $width = 200;
        $height = 100;
        $fileName = md5(uniqid()) . '.jpg';
        $path = __DIR__ . '/' . $fileName;

        $image = imagecreatetruecolor($width, $height);
        imagejpeg($image, $path, 85);

        return [
            [$fileName, $width, $height],
        ];
    }


    /**
     * @param string $filePath
     * @param string $fileKey
     * @param string $linkKey
     * @param string $linkPath
     *
     * @dataProvider provideTestDataForSymlinkMethod
     */
    public function testSymlink($filePath, $fileKey, $linkKey, $linkPath)
    {
        file_put_contents($filePath, 'test');

        $this->filesystem->symlink($fileKey, $linkKey);

        $this->assertTrue(is_link($linkPath));
        $this->assertEquals($filePath, readlink($linkPath));

        unlink($linkPath);
    }


    public function provideTestDataForSymlinkMethod()
    {
        return [
            [__DIR__ . '/' . self::FILENAME, self::FILENAME, 'link', __DIR__ . '/link'],
        ];
    }


    /**
     * {@inheritDoc}
     */
    protected function tearDown(): void
    {
        if (file_exists(__DIR__ . '/' . self::FILENAME)) {
            unlink(__DIR__ . '/' . self::FILENAME);
        }
    }
}
