<?php

declare(strict_types=1);

namespace App\Module\Directory\Infrastructure\ApiPlatform\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Directory\Domain\Entity\CommercialReport;
use App\Module\Directory\Domain\Entity\ReportDocument;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Uid\Uuid;
use Throwable;

use function in_array;

/**
 * @implements ProcessorInterface<ReportDocument, ReportDocument>
 */
final readonly class ReportDocumentProcessor implements ProcessorInterface
{
    private const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB

    private const ALLOWED_MIME_TYPES = [
        'image/jpeg', 'image/png', 'image/gif', 'image/webp',
        'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'application/vnd.ms-excel',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'application/vnd.ms-powerpoint',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'text/plain', 'text/csv',
        'application/zip', 'application/x-rar-compressed', 'application/gzip',
        'application/json', 'application/xml', 'text/xml',
    ];

    private const MIME_TO_EXTENSION = [
        'image/jpeg'                                                                => 'jpg',
        'image/png'                                                                 => 'png',
        'image/gif'                                                                 => 'gif',
        'image/webp'                                                                => 'webp',
        'application/pdf'                                                           => 'pdf',
        'application/msword'                                                        => 'doc',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document'   => 'docx',
        'application/vnd.ms-excel'                                                  => 'xls',
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'         => 'xlsx',
        'application/vnd.ms-powerpoint'                                             => 'ppt',
        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
        'text/plain'                                                                => 'txt',
        'text/csv'                                                                  => 'csv',
        'application/zip'                                                           => 'zip',
        'application/x-rar-compressed'                                              => 'rar',
        'application/gzip'                                                          => 'gz',
        'application/json'                                                          => 'json',
        'application/xml'                                                           => 'xml',
        'text/xml'                                                                  => 'xml',
    ];

    public function __construct(
        private EntityManagerInterface $entityManager,
        private Security $security,
        private RequestStack $requestStack,
        private string $uploadDir,
    ) {}

    /**
     * @param ReportDocument $data
     */
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ReportDocument
    {
        if (!$this->security->isGranted('ROLE_ADMIN')) {
            throw new AccessDeniedHttpException('Creating report documents requires admin privileges.');
        }

        $request = $this->requestStack->getCurrentRequest();

        if (null === $request) {
            throw new BadRequestHttpException('No request available.');
        }

        $document = $this->createUpload($request);
        $document->setCreatedAt(new DateTimeImmutable());
        $document->setUploadedBy($this->security->getUser());

        try {
            $this->entityManager->persist($document);
            $this->entityManager->flush();
        } catch (Throwable $e) {
            $filePath = $this->uploadDir.'/'.$document->getFileName();

            if (file_exists($filePath)) {
                @unlink($filePath);
            }

            throw $e;
        }

        return $document;
    }

    private function createUpload(Request $request): ReportDocument
    {
        $file = $request->files->get('file');

        if (null === $file || !$file->isValid()) {
            throw new BadRequestHttpException('No valid file uploaded.');
        }

        if ($file->getSize() > self::MAX_FILE_SIZE) {
            throw new BadRequestHttpException('File size exceeds 50 MB limit.');
        }

        $report = $this->resolveReport((string) $request->request->get('commercialReport', ''));

        $originalName = $file->getClientOriginalName();
        $mimeType     = $file->getMimeType() ?: 'application/octet-stream';
        $fileSize     = $file->getSize();

        if (!in_array($mimeType, self::ALLOWED_MIME_TYPES, true)) {
            throw new BadRequestHttpException(sprintf('File type "%s" is not allowed.', $mimeType));
        }

        $extension = self::MIME_TO_EXTENSION[$mimeType] ?? 'bin';
        $fileName  = Uuid::v4()->toRfc4122().'.'.$extension;

        if (!is_dir($this->uploadDir)) {
            mkdir($this->uploadDir, 0o775, true);
        }

        $file->move($this->uploadDir, $fileName);

        $document = new ReportDocument();
        $document->setCommercialReport($report);
        $document->setOriginalName($originalName);
        $document->setFileName($fileName);
        $document->setMimeType($mimeType);
        $document->setSize($fileSize);

        return $document;
    }

    private function resolveReport(string $iri): CommercialReport
    {
        $idString = basename($iri);

        if ('' === $iri || !ctype_digit($idString)) {
            throw new BadRequestHttpException('A valid commercialReport IRI is required.');
        }

        $report = $this->entityManager->getRepository(CommercialReport::class)->find((int) $idString);

        if (null === $report) {
            throw new BadRequestHttpException('Commercial report not found.');
        }

        return $report;
    }
}
