<?php

namespace Velis\Test\Cache;

use Exception;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\SimpleCache\CacheInterface;
use Velis\Cache\FileCache;

/**
 * @author Jan Małysiak <jan.malysiak@velis.pl>
 */
class FileCacheTest extends TestCase
{
    /**
     * @var FileCache
     */
    private $cache;

    /**
     * @var MockObject
     */
    private $implementationMock;

    protected function setUp(): void
    {
        $this->implementationMock = $this->createMock(CacheInterface::class);
        $this->cache = new FileCache($this->implementationMock, true);
    }

    public function testGetWhenEntryExists()
    {
        $expectedValue = md5(time());

        $this->implementationMock->expects($this->once())
            ->method('has')
            ->willReturn(true);

        $this->implementationMock->expects($this->once())
            ->method('get')
            ->willReturn($expectedValue);

        $actualValue = $this->cache->get('key');

        $this->assertEquals($expectedValue, $actualValue);
    }

    public function testGetWhenEntryDoesNotExist()
    {
        $this->implementationMock->expects($this->once())
            ->method('has')
            ->willReturn(false);

        $this->implementationMock->expects($this->never())
            ->method('get');

        $this->cache->get('key');
    }

    public function testGetWhenImplementationThrowsException()
    {
        $this->implementationMock->expects($this->once())
            ->method('has')
            ->willReturn(true);

        $this->implementationMock->expects($this->once())
            ->method('get')
            ->willThrowException(new Exception());

        $value = $this->cache->get('key');

        $this->assertNull($value);
    }

    public function testGetProfilerAfterGetCall()
    {
        $key = 'key';
        $execTime = 0.2;

        $this->implementationMock->expects($this->once())
            ->method('has')
            ->willReturn(true);

        $this->implementationMock->expects($this->once())
            ->method('get')
            ->willReturnCallback(function () use ($execTime) {
                usleep($execTime * pow(10, 6));
            });

        $this->cache->get($key);

        $profiler = $this->cache->getProfiler();

        $this->assertIsArray($profiler);
        foreach ($profiler as $profilerRow) {
            $this->assertEquals('get', $profilerRow['func']);
            $this->assertIsArray($profilerRow['caller']);
            $this->assertEquals($key, $profilerRow['id']);
            $this->assertIsFloat($profilerRow['startTime']);
            $this->assertIsFloat($profilerRow['execTime']);
            $this->assertGreaterThan($execTime, $profilerRow['execTime']);
        }
    }

    public function testSet()
    {
        $this->implementationMock->expects($this->once())
            ->method('set')
            ->willReturn(true);

        $result = $this->cache->set('key', 'value');

        $this->assertTrue($result);
    }

    public function testSetWhenImplementationThrowsException()
    {
        $counter = 0;

        $this->implementationMock->expects($this->exactly(2))
            ->method('set')
            ->willReturnCallback(function () use (&$counter) {
                $counter++;

                if (1 == $counter) {
                    throw new Exception();
                }

                return true;
            })
        ;

        $this->implementationMock->expects($this->once())
            ->method('delete');

        $result = $this->cache->set('key', 'value');

        $this->assertTrue($result);
    }
}
