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

use Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\TempUrlOpenstackGenerator;
use Chill\DocStoreBundle\AsyncUpload\SignedUrl;
use Chill\DocStoreBundle\AsyncUpload\SignedUrlPost;
use Psr\Log\NullLogger;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class TempUrlOpenstackGeneratorTest extends KernelTestCase
{
    private ParameterBagInterface $parameterBag;
    private HttpClientInterface $client;

    private const TESTING_OBJECT_NAME_PREFIX = 'test-prefix-o0o008wk404gcos40k8s4s4c44cgwwos4k4o8k/';
    private const TESTING_OBJECT_NAME = 'object-name-4fI0iAtq';

    private function setUpIntegration(): void
    {
        self::bootKernel();
        $this->parameterBag = self::getContainer()->get(ParameterBagInterface::class);
        $this->client = self::getContainer()->get(HttpClientInterface::class);
    }

    /**
     * @dataProvider dataProviderGenerate
     */
    public function testGenerate(string $baseUrl, \DateTimeImmutable $now, string $key, string $method, string $objectName, int $expireDelay, SignedUrl $expected): void
    {
        $logger = new NullLogger();
        $eventDispatcher = new EventDispatcher();
        $clock = new MockClock($now);
        $parameters = new ParameterBag(
            [
                'chill_doc_store' => [
                    'openstack' => [
                        'temp_url' => [
                            'temp_url_key' => $key,
                            'temp_url_base_path' => $baseUrl,
                            'max_post_file_size' => 150,
                            'max_post_file_count' => 1,
                            'max_expires_delay' => 1800,
                            'max_submit_delay' => 1800,
                        ],
                    ],
                ],
            ]
        );

        $generator = new TempUrlOpenstackGenerator(
            $logger,
            $eventDispatcher,
            $clock,
            $parameters,
        );

        $signedUrl = $generator->generate($method, $objectName, $expireDelay);

        self::assertEquals($expected, $signedUrl);
    }

    public static function dataProviderGenerate(): iterable
    {
        $now = \DateTimeImmutable::createFromFormat('U', '1702041743');
        $expireDelay = 1800;
        $baseUrls = [
            'https://objectstore.example/v1/my_account/container/',
            'https://objectstore.example/v1/my_account/container',
        ];
        $objectName = 'object';
        $method = 'GET';
        $key = 'MYKEY';

        $signedUrl = new SignedUrl(
            'GET',
            'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543',
            \DateTimeImmutable::createFromFormat('U', '1702043543'),
            $objectName
        );

        foreach ($baseUrls as $baseUrl) {
            yield [
                $baseUrl,
                $now,
                $key,
                $method,
                $objectName,
                $expireDelay,
                $signedUrl,
            ];
        }
    }

    /**
     * @dataProvider dataProviderGeneratePost
     */
    public function testGeneratePost(string $baseUrl, \DateTimeImmutable $now, string $key, string $method, string $objectName, int $expireDelay, SignedUrl $expected): void
    {
        $logger = new NullLogger();
        $eventDispatcher = new EventDispatcher();
        $clock = new MockClock($now);
        $parameters = new ParameterBag(
            [
                'chill_doc_store' => [
                    'openstack' => [
                        'temp_url' => [
                            'temp_url_key' => $key,
                            'temp_url_base_path' => $baseUrl,
                            'max_post_file_size' => 150,
                            'max_post_file_count' => 1,
                            'max_expires_delay' => 1800,
                            'max_submit_delay' => 1800,
                        ],
                    ],
                ],
            ]
        );

        $generator = new TempUrlOpenstackGenerator(
            $logger,
            $eventDispatcher,
            $clock,
            $parameters,
        );

        $signedUrl = $generator->generatePost();

        self::assertEquals('POST', $signedUrl->method);
        self::assertEquals((int) $clock->now()->format('U') + 1800, $signedUrl->expires->getTimestamp());
        self::assertEquals(150, $signedUrl->max_file_size);
        self::assertEquals(1, $signedUrl->max_file_count);
        self::assertEquals(1800, $signedUrl->submit_delay);
        self::assertEquals('', $signedUrl->redirect);
        self::assertGreaterThanOrEqual(20, strlen($signedUrl->prefix));
    }

    public static function dataProviderGeneratePost(): iterable
    {
        $now = \DateTimeImmutable::createFromFormat('U', '1702041743');
        $expireDelay = 1800;
        $baseUrls = [
            'https://objectstore.example/v1/my_account/container/',
            'https://objectstore.example/v1/my_account/container',
        ];
        $objectName = 'object';
        $method = 'GET';
        $key = 'MYKEY';

        $signedUrl = new SignedUrlPost(
            'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543',
            \DateTimeImmutable::createFromFormat('U', '1702043543'),
            $objectName,
            150,
            1,
            1800,
            '',
            'abc',
            'abc'
        );

        foreach ($baseUrls as $baseUrl) {
            yield [
                $baseUrl,
                $now,
                $key,
                $method,
                $objectName,
                $expireDelay,
                $signedUrl,
            ];
        }
    }

    /**
     * @group openstack-integration
     */
    public function testGeneratePostIntegration(): void
    {
        $this->setUpIntegration();
        $generator = new TempUrlOpenstackGenerator(new NullLogger(), new EventDispatcher(), new MockClock(), $this->parameterBag);

        $signedUrl = $generator->generatePost(object_name: self::TESTING_OBJECT_NAME_PREFIX);
        $formData = new FormDataPart([
            'redirect', $signedUrl->redirect,
            'max_file_size' => (string) $signedUrl->max_file_size,
            'max_file_count' => (string) $signedUrl->max_file_count,
            'expires' => (string) $signedUrl->expires->getTimestamp(),
            'signature' => $signedUrl->signature,
            self::TESTING_OBJECT_NAME => DataPart::fromPath(
                __DIR__.'/file-to-upload.txt',
                self::TESTING_OBJECT_NAME
            ),
        ]);

        $response = $this->client
            ->request(
                'POST',
                $signedUrl->url,
                [
                    'body' => $formData->bodyToString(),
                    'headers' => $formData->getPreparedHeaders()->toArray(),
                ]
            );

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

    /**
     * @group openstack-integration
     *
     * @depends testGeneratePostIntegration
     */
    public function testGenerateGetIntegration(): void
    {
        $this->setUpIntegration();
        $generator = new TempUrlOpenstackGenerator(new NullLogger(), new EventDispatcher(), new MockClock(), $this->parameterBag);

        $signedUrl = $generator->generate('GET', self::TESTING_OBJECT_NAME_PREFIX.self::TESTING_OBJECT_NAME);

        $response = $this->client->request('GET', $signedUrl->url);

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

        try {
            $content = $response->getContent();
            self::assertEquals(file_get_contents(__DIR__.'/file-to-upload.txt'), $content);
        } catch (HttpExceptionInterface $exception) {
            $this->fail('could not retrieve file content: '.$exception->getMessage());
        }
    }
}
