<?php

namespace Velis\Test\Filesystem\Adapter;

use Aws\S3\S3Client;
use Gaufrette\File;
use Gaufrette\FilesystemInterface;
use Velis\Cache\CacheInterface;
use Velis\Filesystem\Adapter\AwsS3;
use Velis\Filesystem\Av\Concrete\AvScanResult;
use Velis\Filesystem\Av\Contract\AvClientInterface;
use Velis\Filesystem\Av\Exception\OneOrMoreFilesInfectedException;
use Velis\TestCase;

/**
 * @author Szymon Janaczek <szymon.janaczek@velistech.com>
 */
final class AwsS3Test extends TestCase
{
    public function testUploadWithEnabledAvInfectedFile(): void
    {
        $filePath = $this->getTmpFilePath();
        $avScanResult = new AvScanResult(
            isInfected: true,
        );

        $this->expectException(OneOrMoreFilesInfectedException::class);

        $avClientMock = $this->createMock(AvClientInterface::class);
        $awsS3 = new AwsS3(
            $this->createMock(FilesystemInterface::class),
            $this->createMock(S3Client::class),
            'bucket',
            $this->createMock(CacheInterface::class),
            3600,
            $avClientMock,
        );

        $avClientMock->expects($this->once())
            ->method('fileScanInStream')
            ->with($filePath)
            ->willReturn($avScanResult);

        $awsS3->upload('file', $filePath);

        $this->assertFileDoesNotExist($filePath);
    }

    public function testUploadWithEnabledAvNotInfectedFile(): void
    {
        $filePath = $this->getTmpFilePath();
        $avScanResult = new AvScanResult(
            isInfected: false,
        );

        $avClientMock = $this->createMock(AvClientInterface::class);

        // I know, mocking object we want to test may not be the best idea, but it's the only way to test this method,
        // because of existing dependencies.
        $awsS3 = $this->getMockBuilder(AwsS3::class)
            ->setConstructorArgs([
                $this->createMock(FilesystemInterface::class),
                $this->createMock(S3Client::class),
                'bucket',
                $this->createMock(CacheInterface::class),
                3600,
                $avClientMock,
            ])
            ->onlyMethods(['get'])
            ->getMock();

        $fileMock = $this->createMock(File::class);
        $awsS3->expects($this->once())
            ->method('get')
            ->willReturn($fileMock);

        $fileMock->expects($this->once())
            ->method('setContent');

        $avClientMock->expects($this->once())
            ->method('fileScanInStream')
            ->with($filePath)
            ->willReturn($avScanResult);

        $awsS3->upload('file', $filePath);

        $this->assertFileDoesNotExist($filePath);
    }

    /**
     * In this test, I want to check if upload method works correctly when AV is disabled.
     * "AV disabled" means that $avClientMock is null.
     */
    public function testUploadWithDisabledAv(): void
    {
        $filePath = $this->getTmpFilePath();

        $avClientMock = null;

        // I know, mocking object we want to test may not be the best idea, but it's the only way to test this method,
        // because of existing dependencies.
        $awsS3 = $this->getMockBuilder(AwsS3::class)
            ->setConstructorArgs([
                $this->createMock(FilesystemInterface::class),
                $this->createMock(S3Client::class),
                'bucket',
                $this->createMock(CacheInterface::class),
                3600,
                $avClientMock,
            ])
            ->onlyMethods(['get'])
            ->getMock();

        $awsS3->expects($this->once())
            ->method('get')
            ->willReturn($this->createMock(File::class));

        $awsS3->upload('file', $filePath);

        $this->assertFileDoesNotExist($filePath);
    }

    /**
     * @return mixed
     */
    public function getTmpFilePath(): mixed
    {
        $tmpFile = tmpfile();
        $filePath = stream_get_meta_data($tmpFile)['uri'];
        $this->assertFileExists($filePath, 'Could not create a test file.');

        return $filePath;
    }
}
