<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\DocStoreBundle\Tests\Controller;

use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager;
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator;
use Chill\DocStoreBundle\Controller\StoredObjectContentToLocalStorageController;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * @internal
 *
 * @coversNothing
 */
class StoredObjectContentToLocalStorageControllerTest extends TestCase
{
    use ProphecyTrait;

    /**
     * @dataProvider generateOperateContentWithExceptionDataProvider
     */
    public function testOperateContentWithException(Request $request, string $expectedException, string $expectedExceptionMessage, bool $existContent, string $readContent, bool $signatureValidity): void
    {
        $storedObjectManager = $this->prophesize(StoredObjectManager::class);
        $storedObjectManager->existsContent(Argument::any())->willReturn($existContent);
        $storedObjectManager->readContent(Argument::any())->willReturn($readContent);

        $tempUrlLocalStorageGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class);
        $tempUrlLocalStorageGenerator->validateSignature(
            $request->query->get('sig', ''),
            $request->getMethod(),
            $request->query->get('object_name', ''),
            $request->query->getInt('exp', 0)
        )
            ->willReturn($signatureValidity);

        $this->expectException($expectedException);
        $this->expectExceptionMessage($expectedExceptionMessage);

        $controller = new StoredObjectContentToLocalStorageController(
            $storedObjectManager->reveal(),
            $tempUrlLocalStorageGenerator->reveal()
        );

        $controller->contentOperate($request);
    }

    public static function generateOperateContentWithExceptionDataProvider(): iterable
    {
        yield [
            new Request(['object_name' => '', 'sig' => '', 'exp' => 0]),
            BadRequestHttpException::class,
            'Object name parameter is missing',
            false,
            '',
            false,
        ];

        yield [
            new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => 0]),
            BadRequestHttpException::class,
            'Expiration is not set or equal to zero',
            false,
            '',
            false,
        ];

        yield [
            new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
            BadRequestHttpException::class,
            'Signature is not set or is a blank string',
            false,
            '',
            false,
        ];

        yield [
            new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
            AccessDeniedHttpException::class,
            'Invalid signature',
            false,
            '',
            false,
        ];


        yield [
            new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]),
            NotFoundHttpException::class,
            'Object does not exists on disk',
            false,
            '',
            true,
        ];
    }

    public function testOperateContentGetHappyScenario(): void
    {
        $objectName = 'testABC';
        $expiration = new \DateTimeImmutable();
        $storedObjectManager = $this->prophesize(StoredObjectManager::class);
        $storedObjectManager->existsContent($objectName)->willReturn(true);
        $storedObjectManager->readContent($objectName)->willReturn('123456789');

        $tempUrlLocalStorageGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class);
        $tempUrlLocalStorageGenerator->validateSignature('signature', 'GET', $objectName, $expiration->getTimestamp())
            ->shouldBeCalled()
            ->willReturn(true);

        $controller = new StoredObjectContentToLocalStorageController(
            $storedObjectManager->reveal(),
            $tempUrlLocalStorageGenerator->reveal()
        );

        $response = $controller->contentOperate(new Request(['object_name' => $objectName, 'sig' => 'signature', 'exp' => $expiration->getTimestamp()]));

        self::assertEquals(200, $response->getStatusCode());
        self::assertEquals('123456789', $response->getContent());
    }

    public function testOperateContentHeadHappyScenario(): void
    {
        $objectName = 'testABC';
        $expiration = new \DateTimeImmutable();
        $storedObjectManager = $this->prophesize(StoredObjectManager::class);
        $storedObjectManager->existsContent($objectName)->willReturn(true);
        $storedObjectManager->readContent($objectName)->willReturn('123456789');

        $tempUrlLocalStorageGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class);
        $tempUrlLocalStorageGenerator->validateSignature('signature', 'HEAD', $objectName, $expiration->getTimestamp())
            ->shouldBeCalled()
            ->willReturn(true);

        $controller = new StoredObjectContentToLocalStorageController(
            $storedObjectManager->reveal(),
            $tempUrlLocalStorageGenerator->reveal()
        );

        $request = new Request(['object_name' => $objectName, 'sig' => 'signature', 'exp' => $expiration->getTimestamp()]);
        $request->setMethod('HEAD');
        $response = $controller->contentOperate($request);

        self::assertEquals(200, $response->getStatusCode());
        self::assertEquals('', $response->getContent());
    }

    public function testPostContentHappyScenario(): void
    {
        $expiration = 171899000;
        $storedObjectManager = $this->prophesize(StoredObjectManager::class);
        $storedObjectManager->writeContent('filePrefix/abcSUFFIX', Argument::containingString('fake_encrypted_content'))
            ->shouldBeCalled();

        $tempUrlGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class);
        $tempUrlGenerator->validateSignaturePost('signature', 'filePrefix/abc', $expiration, 15_000_000, 1)
            ->shouldBeCalled()
            ->willReturn(true);

        $controller = new StoredObjectContentToLocalStorageController($storedObjectManager->reveal(), $tempUrlGenerator->reveal());

        $request = new Request(
            ['prefix' => 'filePrefix/abc'],
            ['signature' => 'signature', 'expires' => $expiration, 'max_file_size' => 15_000_000, 'max_file_count' => 1],
            files: [
                'filePrefix/abcSUFFIX' => new UploadedFile(
                    __DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file',
                    'Document.odt',
                    test: true
                ),
            ]
        );

        $response = $controller->postContent($request);

        self::assertEquals(204, $response->getStatusCode());
    }

    /**
     * @dataProvider generatePostContentWithExceptionDataProvider
     */
    public function testPostContentWithException(Request $request, bool $isSignatureValid, string $expectedException, string $expectedExceptionMessage): void
    {
        $storedObjectManager = $this->prophesize(StoredObjectManager::class);
        $storedObjectManager->writeContent(Argument::any(), Argument::any())->shouldNotBeCalled();

        $tempUrlGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class);
        $tempUrlGenerator->validateSignaturePost('signature', Argument::any(), Argument::any(), Argument::any(), Argument::any())
            ->willReturn($isSignatureValid);

        $controller = new StoredObjectContentToLocalStorageController(
            $storedObjectManager->reveal(),
            $tempUrlGenerator->reveal()
        );

        $this->expectException($expectedException);
        $this->expectExceptionMessage($expectedExceptionMessage);

        $controller->postContent($request);
    }

    public static function generatePostContentWithExceptionDataProvider(): iterable
    {
        $query = ['prefix' => 'filePrefix/abc'];
        $attributes = ['signature' => 'signature', 'expires' => 15088556855, 'max_file_size' => 15_000_000, 'max_file_count' => 1];

        $request = new Request([]);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            BadRequestHttpException::class,
            'Prefix parameter is missing',
        ];


        $attrCloned = [...$attributes];
        unset($attrCloned['max_file_size']);
        $request = new Request($query, $attrCloned);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            BadRequestHttpException::class,
            'Max file size is not set or equal to zero',
        ];

        $attrCloned = [...$attributes];
        unset($attrCloned['max_file_count']);
        $request = new Request($query, $attrCloned);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            BadRequestHttpException::class,
            'Max file count is not set or equal to zero',
        ];

        $attrCloned = [...$attributes];
        unset($attrCloned['expires']);
        $request = new Request($query, $attrCloned);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            BadRequestHttpException::class,
            'Expiration is not set or equal to zero',
        ];

        $attrCloned = [...$attributes];
        unset($attrCloned['signature']);
        $request = new Request($query, $attrCloned);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            BadRequestHttpException::class,
            'Signature is not set or is a blank string',
        ];

        $request = new Request($query, $attributes);
        $request->setMethod('POST');

        yield [
            $request,
            false,
            AccessDeniedHttpException::class,
            'Invalid signature',
        ];

        $request = new Request($query, $attributes, files: []);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            BadRequestHttpException::class,
            'Zero files given',
        ];

        $request = new Request($query, $attributes, files: [
            'filePrefix/abcSUFFIX_1' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content', test: true),
            'filePrefix/abcSUFFIX_2' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content2', test: true),
        ]);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            AccessDeniedHttpException::class,
            'More files than max file count',
        ];

        $request = new Request($query, [...$attributes, 'max_file_size' => 3], files: [
            'filePrefix/abcSUFFIX_1' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content', test: true),
        ]);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            AccessDeniedHttpException::class,
            'File is too big',
        ];

        $request = new Request($query, [...$attributes], files: [
            'some/other/prefix_SUFFIX' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content', test: true),
        ]);
        $request->setMethod('POST');

        yield [
            $request,
            true,
            AccessDeniedHttpException::class,
            'Filename does not start with signed prefix',
        ];
    }
}
