<?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\AsyncUpload\Driver\LocalStorage;

use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

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

    private const SECRET = 'abc';

    public function testGenerate(): void
    {
        $urlGenerator = $this->prophesize(UrlGeneratorInterface::class);
        $urlGenerator->generate('chill_docstore_stored_object_operate', [
            'object_name' => $object_name = 'testABC',
            'exp' => $expiration = 1734307200 + 180,
            'sig' => TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name, $expiration),
        ], UrlGeneratorInterface::ABSOLUTE_URL)
            ->shouldBeCalled()
            ->willReturn($url = 'http://example.com/public/doc-store/stored-object/operate/testABC');

        $generator = $this->buildGenerator($urlGenerator->reveal());

        $signedUrl = $generator->generate('GET', $object_name);

        self::assertEquals($url, $signedUrl->url);
        self::assertEquals($object_name, $signedUrl->object_name);
        self::assertEquals($expiration, $signedUrl->expires->getTimestamp());
        self::assertEquals('GET', $signedUrl->method);
    }

    public function testGeneratePost(): void
    {
        $urlGenerator = $this->prophesize(UrlGeneratorInterface::class);
        $urlGenerator->generate('chill_docstore_storedobject_post', [
            'prefix' => 'prefixABC',
        ], UrlGeneratorInterface::ABSOLUTE_URL)
            ->shouldBeCalled()
            ->willReturn($url = 'http://example.com/public/doc-store/stored-object/prefixABC');

        $generator = $this->buildGenerator($urlGenerator->reveal());

        $signedUrl = $generator->generatePost(object_name: 'prefixABC');

        self::assertEquals($url, $signedUrl->url);
        self::assertEquals('prefixABC', $signedUrl->object_name);
        self::assertEquals($expiration = 1734307200 + 180 + 180, $signedUrl->expires->getTimestamp());
        self::assertEquals('POST', $signedUrl->method);
        self::assertEquals(TempUrlLocalStorageGeneratorTest::expectedSignature('POST', 'prefixABC', $expiration), $signedUrl->signature);
    }

    private static function expectedSignature(string $method, $objectName, int $expiration): string
    {
        return hash('sha512', sprintf('%s.%s.%s.%d', $method, self::SECRET, $objectName, $expiration));
    }

    /**
     * @dataProvider generateValidateSignatureData
     */
    public function testValidateSignature(string $signature, string $method, string $objectName, int $expiration, \DateTimeImmutable $now, bool $expected, string $message): void
    {
        $urlGenerator = $this->buildGenerator(clock: new MockClock($now));

        self::assertEquals($expected, $urlGenerator->validateSignature($signature, $method, $objectName, $expiration), $message);
    }

    public static function generateValidateSignatureData(): iterable
    {
        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
            'GET',
            $object_name,
            $expiration,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            true,
            'Valid signature, not expired',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('HEAD', $object_name = 'testABC', $expiration = 1734307200 + 180),
            'HEAD',
            $object_name,
            $expiration,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            true,
            'Valid signature, not expired',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180).'A',
            'GET',
            $object_name,
            $expiration,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Invalid signature',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
            'GET',
            $object_name,
            $expiration,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)),
            false,
            'Signature expired',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180),
            'GET',
            $object_name.'____',
            $expiration,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Invalid object name',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
            'POST',
            $object_name,
            $expiration,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Wrong method',
        ];
    }

    /**
     * @dataProvider generateValidateSignaturePostData
     */
    public function testValidateSignaturePost(string $signature, int $expiration, string $objectName, int $maxFileSize, int $maxFileCount, \DateTimeImmutable $now, bool $expected, string $message): void
    {
        $urlGenerator = $this->buildGenerator(clock: new MockClock($now));

        self::assertEquals($expected, $urlGenerator->validateSignaturePost($signature, $objectName, $expiration, $maxFileSize, $maxFileCount), $message);
    }

    public static function generateValidateSignaturePostData(): iterable
    {
        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
            $expiration,
            $object_name,
            15_000_000,
            1,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            true,
            'Valid signature',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
            $expiration,
            $object_name,
            15_000_001,
            1,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Wrong max file size',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
            $expiration,
            $object_name,
            15_000_000,
            2,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Wrong max file count',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
            $expiration,
            $object_name.'AAA',
            15_000_000,
            1,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Invalid object name',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180).'A',
            $expiration,
            $object_name,
            15_000_000,
            1,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)),
            false,
            'Invalid signature',
        ];

        yield [
            TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180),
            $expiration,
            $object_name,
            15_000_000,
            1,
            \DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)),
            false,
            'Expired signature',
        ];
    }

    private function buildGenerator(?UrlGeneratorInterface $urlGenerator = null, ?ClockInterface $clock = null): TempUrlLocalStorageGenerator
    {
        return new TempUrlLocalStorageGenerator(
            self::SECRET,
            $clock ?? new MockClock('2024-12-16T00:00:00+00:00'),
            $urlGenerator ?? $this->prophesize(UrlGeneratorInterface::class)->reveal(),
        );
    }
}
