<?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\Controller;

use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\MainBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;

final readonly class AsyncUploadController
{
    public function __construct(
        private TempUrlGeneratorInterface $tempUrlGenerator,
        private SerializerInterface $serializer,
        private Security $security,
        private LoggerInterface $chillLogger,
    ) {}

    #[Route(path: '/api/1.0/doc-store/async-upload/temp_url/{uuid}/generate/post', name: 'chill_docstore_asyncupload_getsignedurlpost')]
    public function getSignedUrlPost(Request $request, StoredObject $storedObject): JsonResponse
    {
        if (!$this->security->isGranted(StoredObjectRoleEnum::EDIT->value, $storedObject)) {
            throw new AccessDeniedHttpException('not able to edit the given stored object');
        }

        // we create a dummy version, to generate a filename
        $version = $storedObject->registerVersion();

        $p = $this->tempUrlGenerator
            ->generatePost(
                $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null,
                $request->query->has('submit_delay') ? $request->query->getInt('submit_delay') : null,
                object_name: $version->getFilename()
            );

        $this->chillLogger->notice('[Privacy Event] a request to upload a document has been generated', [
            'doc_uuid' => $storedObject->getUuid(),
        ]);

        return new JsonResponse(
            $this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]),
            Response::HTTP_OK,
            [],
            true
        );
    }

    #[Route(path: '/api/1.0/doc-store/async-upload/temp_url/{uuid}/generate/{method}', name: 'chill_docstore_asyncupload_getsignedurlget', requirements: ['method' => 'get|head'])]
    public function getSignedUrlGet(Request $request, StoredObject $storedObject, string $method): JsonResponse
    {
        if (!$this->security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)) {
            throw new AccessDeniedHttpException('not able to read the given stored object');
        }

        // we really want to be sure that there are no other method than get or head:
        if (!in_array($method, ['get', 'head'], true)) {
            throw new AccessDeniedHttpException('Only methods get and head are allowed');
        }

        if ($request->query->has('version')) {
            $filename = $request->query->get('version');

            $storedObjectVersion = $storedObject->getVersions()->findFirst(fn (int $index, StoredObjectVersion $version): bool => $version->getFilename() === $filename);

            if (null === $storedObjectVersion) {
                // we are here in the case where the version is not stored into the database
                // as the version is prefixed by the stored object prefix, we just have to check that this prefix
                // is the same. It means that the user had previously the permission to "SEE_AND_EDIT" this stored
                // object with same prefix that we checked before
                if (!str_starts_with($filename, $storedObject->getPrefix())) {
                    throw new AccessDeniedHttpException('not able to match the version with the same filename');
                }
            }
        } else {
            $filename = $storedObject->getCurrentVersion()->getFilename();
        }

        $p = $this->tempUrlGenerator->generate(
            $method,
            $filename,
            $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null
        );

        $user = $this->security->getUser();
        $userId = match ($user instanceof User) {
            true => $user->getId(),
            false => $user->getUserIdentifier(),
        };

        $this->chillLogger->notice('[Privacy Event] a request to see a document has been granted', [
            'doc_uuid' => $storedObject->getUuid()->toString(),
            'user_id' => $userId,
        ]);

        return new JsonResponse(
            $this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]),
            Response::HTTP_OK,
            [],
            true
        );
    }
}
