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

use Chill\DocStoreBundle\AsyncUpload\SignedUrl;
use Chill\DocStoreBundle\AsyncUpload\SignedUrlPost;
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class TempUrlLocalStorageGenerator implements TempUrlGeneratorInterface
{
    private const SIGNATURE_DURATION = 180;

    public function __construct(
        private readonly string $secret,
        private readonly ClockInterface $clock,
        private readonly UrlGeneratorInterface $urlGenerator,
    ) {}

    public function generate(string $method, string $object_name, ?int $expire_delay = null): SignedUrl
    {
        $expiration = $this->clock->now()->getTimestamp() + min($expire_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION);

        return new SignedUrl(
            strtoupper($method),
            $this->urlGenerator->generate('chill_docstore_stored_object_operate', [
                'object_name' => $object_name,
                'exp' => $expiration,
                'sig' => $this->sign(strtoupper($method), $object_name, $expiration),
            ], UrlGeneratorInterface::ABSOLUTE_URL),
            \DateTimeImmutable::createFromFormat('U', (string) $expiration),
            $object_name,
        );
    }

    public function generatePost(?int $expire_delay = null, ?int $submit_delay = null, int $max_file_count = 1, ?string $object_name = null): SignedUrlPost
    {
        $submitDelayComputed = min($submit_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION);
        $expireDelayComputed = min($expire_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION);
        $objectNameComputed = $object_name ?? StoredObject::generatePrefix();
        $expiration = $this->clock->now()->getTimestamp() + $expireDelayComputed + $submitDelayComputed;

        return new SignedUrlPost(
            $this->urlGenerator->generate(
                'chill_docstore_storedobject_post',
                ['prefix' => $objectNameComputed],
                UrlGeneratorInterface::ABSOLUTE_URL
            ),
            \DateTimeImmutable::createFromFormat('U', (string) $expiration),
            $objectNameComputed,
            15_000_000,
            1,
            $submitDelayComputed,
            '',
            $objectNameComputed,
            $this->sign('POST', $object_name, $expiration),
        );
    }

    private function sign(string $method, string $object_name, int $expiration): string
    {
        return hash('sha512', sprintf('%s.%s.%s.%d', $method, $this->secret, $object_name, $expiration));
    }

    public function validateSignaturePost(string $signature, string $prefix, int $expiration, int $maxFileSize, int $maxFileCount): bool
    {
        if (15_000_000 !== $maxFileSize || 1 !== $maxFileCount) {
            return false;
        }

        return $this->internalValidateSignature($signature, 'POST', $prefix, $expiration);
    }

    private function internalValidateSignature(string $signature, string $method, string $object_name, int $expiration): bool
    {
        if ($expiration < $this->clock->now()->format('U')) {
            return false;
        }

        if ('' === $object_name) {
            return false;
        }


        return $this->sign($method, $object_name, $expiration) === $signature;
    }

    public function validateSignature(string $signature, string $method, string $objectName, int $expiration): bool
    {
        if (!in_array($method, ['GET', 'HEAD'], true)) {
            return false;
        }

        return $this->internalValidateSignature($signature, $method, $objectName, $expiration);
    }
}
