<?php declare(strict_types=1);

namespace App\Controller\File;

use App\Entity\File;
use App\Entity\User;
use App\Repository\UserRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class UploadController extends AbstractController
{
    public function __construct(
        private readonly UserRepository         $userRepository,
        private readonly EntityManagerInterface $entityManager
    ) {
    }

    #[Route('/upload', name: 'app_upload_file')]
    public function handleFileUpload(Request $request): Response
    {
        if (!$request->isMethod('POST') || !$request->request->has('token') || !$request->files->has('file')) {
            return new JsonResponse(['error' => 'Invalid Request'], Response::HTTP_BAD_REQUEST);
        }

        $uploadedFile = $request->files->get('file');
        $uploadToken = $request->request->get('token');


        $user = $this->getUserByToken($uploadToken);

        if (!$user) {
            return new JsonResponse(['error' => 'Invalid Credentials'], Response::HTTP_BAD_REQUEST);
        }

        if (!$this->isValidUploadedFile($uploadedFile)) {
            return new JsonResponse(['error' => 'Invalid file type'], Response::HTTP_BAD_REQUEST);
        }

        try {
            $destination = $this->createUserUploadDirectory($user->getId());
            $newFilename = $this->generateUniqueFilename($uploadedFile->guessExtension());
            $fileSize = $uploadedFile->getSize();
            $this->moveUploadedFile($uploadedFile, $destination, $newFilename);

            $file = $this->createFileEntity($user, $newFilename, $fileSize, $uploadedFile->getClientOriginalName());
            $this->entityManager->persist($file);
            $this->entityManager->flush();
            $this->setPermissions($destination, $newFilename);

            $fileUrl = $this->generateFileUrl($newFilename);

            return new Response($fileUrl, Response::HTTP_CREATED);
        } catch (Exception $exception) {
            return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
        }
    }

    private function getUserByToken(string $token): ?User
    {
        return $this->userRepository->findOneBy(['token' => $token]);
    }

    private function createUserUploadDirectory(int $userId): string
    {
        $destination = $this->getParameter('kernel.project_dir') . '/public/uploads/' . $userId;
        $this->createDirectory($destination);

        return $destination;
    }

    private function isValidUploadedFile(?UploadedFile $uploadedFile): bool
    {
        if (!$uploadedFile) {
            return false;
        }

        $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'ico', 'svg'];
        $fileExtension = $uploadedFile->guessExtension();

        return in_array($fileExtension, $allowedExtensions);
    }

    private function createDirectory(string $directory): void
    {
        if (!file_exists($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $directory));
        }
    }

    private function generateUniqueFilename(string $extension): string
    {
        return uniqid() . '.' . $extension;
    }

    private function moveUploadedFile(UploadedFile $uploadedFile, string $destination, string $newFilename): void
    {
        $uploadedFile->move($destination, $newFilename);
    }

    private function createFileEntity($user, string $newFilename, int $fileSize, string $originalName): File
    {
        return (new File())
            ->setUser($user)
            ->setName($newFilename)
            ->setSize($fileSize)
            ->setOriginalFilename(
                pathinfo($originalName, PATHINFO_FILENAME)
                . '.'
                . pathinfo($originalName, PATHINFO_EXTENSION)
            )
            ->setCreatedAt(new DateTimeImmutable());
    }

    private function setPermissions(string $destination, string $newFilename): void
    {
        chmod($destination, 0755);
        chmod($destination . '/' . $newFilename, 0644);
        chown($destination . '/' . $newFilename, 'www-data');
    }

    private function generateFileUrl(string $newFilename): string
    {
        return $this->generateUrl(
            route: 'app_serve_file',
            parameters: ['filename' => $newFilename],
            referenceType: UrlGeneratorInterface::ABSOLUTE_URL
        );
    }
}
