<?php

namespace App\Controller;

use App\Entity\Chapter;
use App\Entity\Manga;
use App\Form\MangaEditType;
use App\Manager\FileSystemManager;
use App\Manager\Toolbar\Factory\ToolbarFactory;
use App\Message\DownloadChapter;
use App\Message\RefreshMetadata;
use App\Repository\ChapterRepository;
use App\Repository\ContentSourceRepository;
use App\Repository\MangaRepository;
use App\Service\CbzService;
use App\Service\MangadexProvider;
use App\Service\NotificationService;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Intervention\Image\Drivers\Gd\Driver;
use Intervention\Image\ImageManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\String\Slugger\SluggerInterface;

class MangaController extends AbstractController
{
    private ImageManager $imageManager;

    public function __construct(
        private readonly FileSystemManager       $fileSystemManager,
        private readonly MangaRepository         $mangaRepository,
        private readonly ChapterRepository       $chapterRepository,
        private readonly MessageBusInterface     $bus,
        private readonly CbzService              $cbzService,
        private readonly ToolbarFactory          $toolbarFactory,
        private readonly MangadexProvider        $mangadexProvider,
        private readonly EntityManagerInterface  $entityManager,
        private readonly NotificationService     $notificationService,
        private readonly ContentSourceRepository $contentSourceRepository
    ) {
        $this->imageManager = new ImageManager(new Driver());
    }

    #[Route('/legacy', name: 'app_legacy')]
    public function index(Request $request): Response
    {
        $sort = $request->query->get('sort', 'title');
        $order = $request->query->get('order', 'asc');
        $status = $request->query->get('status', 'all');
        $view = $request->query->get('view', 'poster');

        $mangas = $this->mangaRepository->findAllSortedAndFiltered($sort, $order, $status);

        return $this->render('manga/index.html.twig', [
            'mangas' => $mangas,
            'toolbar' => $this->toolbarFactory->createToolbar('manga_list')->getGroups(),
            'currentStatus' => $status,
            'currentView' => $view,
        ]);
    }

    #[Route('/manga/chapters/{mangaSlug}', name: 'app_manga_show')]
    public function showChapters(string $mangaSlug, Request $request): Response
    {
        //        $manga = $this->mangaRepository->findOneWithChapterBy(['slug' => $mangaSlug]);
        $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);

        if (!$manga) {
            throw new NotFoundHttpException("Le manga demandé n'existe pas.");
        }

        $form = $this->createForm(MangaEditType::class, $manga);
        $contentSources = $this->contentSourceRepository->findAll();

        return $this->render('manga/show_chapters.html.twig', [
            'manga' => $manga,
            'toolbar' => $this->toolbarFactory->createToolbar('chapter_list', ['mangaId' => $manga->getId(), 'isMonitored' => (int)$manga->isMonitored()])->getGroups(),
            'form' => $form->createView(),
            'contentSources' => $contentSources,
        ]);
    }

    #[Route('/manga/delete/{id}', name: 'app_manga_delete', methods: ['DELETE'])]
    public function deleteManga(Manga $manga): JsonResponse
    {
        try {
            foreach ($manga->getChapters() as $chapter) {
                file_exists($chapter->getCbzPath()) ?? unlink($chapter->getCbzPath());
                $this->entityManager->remove($chapter);
            }
            $this->entityManager->remove($manga);
            $this->entityManager->flush();

            return new JsonResponse(['success' => true]);
        } catch (\Exception $e) {
            return new JsonResponse(['success' => false, 'error' => 'Unable to delete manga.'], 500);
        }
    }

    #[Route('/manga/{id}/edit', name: 'app_manga_edit', methods: ['POST'])]
    public function edit(Request $request, Manga $manga, EntityManagerInterface $entityManager): JsonResponse|Response
    {
        $form = $this->createForm(MangaEditType::class, $manga);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->flush();

            return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
        }

        $errors = [];
        foreach ($form->getErrors(true) as $error) {
            $errors[] = $error->getMessage();
        }

        return new JsonResponse(['errors' => $errors], 400);
    }

    #[Route('/manga/{id}/preferred-sources', name: 'manga_preferred_sources', methods: ['POST'])]
    public function updatePreferredSources(
        Request                 $request,
        Manga                   $manga,
        ContentSourceRepository $contentSourceRepository
    ): JsonResponse {
        $data = json_decode($request->getContent(), true);
        $preferredSourceIds = $data['preferredSources'] ?? [];

        $preferredSources = $contentSourceRepository->findBy(['id' => $preferredSourceIds]);

        // This will maintain the order of the sources as they were sent in the request
        $orderedPreferredSources = array_map(
            fn ($id) => current(array_filter($preferredSources, fn ($s) => $s->getId() == $id)),
            $preferredSourceIds
        );

        $manga->setPreferredSources(array_filter($orderedPreferredSources));
        $this->entityManager->flush();

        return new JsonResponse(['success' => true]);
    }

    public function _chaptersByManga(int $id): Response
    {
        $manga = $this->mangaRepository->find($id);
        $chaptersByVolume = [];
        foreach ($manga->getChapters() as $chapter) {
            $volume = $chapter->getVolume() ?? 'Not Found';
            $chaptersByVolume[$volume][] = $chapter;
        }

        foreach ($chaptersByVolume as $volume => &$chapters) {
            usort($chapters, function ($a, $b) {
                return $b->getNumber() <=> $a->getNumber();
            });
        }
        unset($chapters);

        uksort($chaptersByVolume, function ($a, $b) {
            if ($a == 0) {
                return -1;
            }
            if ($b == 0) {
                return 1;
            }
            return $b <=> $a;
        });

        return $this->render('manga/_chapter_list.html.twig', [
            'manga' => $manga,
            'chapters_by_volume' => $chaptersByVolume
        ]);
    }

    #[Route('/delete_cbz/{id}', name: 'app_delete_cbz')]
    public function deleteChapterCbz(Chapter $chapter): JsonResponse
    {
        $cbzPath = $chapter->getCbzPath();
        if (!$cbzPath) {
            return new JsonResponse(['error' => 'No CBZ path for this chapter.'], 400);
        }

        file_exists($cbzPath) ?? unlink($cbzPath);

        $chapter->setCbzPath(null);
        $this->entityManager->persist($chapter);
        $this->entityManager->flush();

        return new JsonResponse(['success' => 'CBZ file deleted.'], 200);
    }

    #[Route('/chapter/{id}/edit', name: 'app_chapter_edit', methods: ['POST'])]
    public function editChapter(Request $request, Chapter $chapter): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

        $chapter->setNumber($data['number']);
        $chapter->setTitle($data['title']);

        $this->entityManager->flush();

        return new JsonResponse(['success' => true, 'message' => 'Chapter updated successfully']);
    }

    #[Route('/hide_chapter/{id}', name: 'app_hide_chapter')]
    public function hideChapter(Chapter $chapter): JsonResponse
    {
        $chapter->setVisible(false);
        $this->entityManager->persist($chapter);
        $this->entityManager->flush();

        return new JsonResponse(['success' => 'Chapter hidden.'], 200);
    }

    #[Route('/manga/search/{query}', name: 'app_manga_search')]
    public function search(string $query = ''): Response
    {
        return $this->render('manga/add_new.html.twig', [
            'query' => $query,
        ]);
    }


    /**
     * @throws GuzzleException
     */
    #[Route('/addManga', name: 'app_manga_add')]
    public function addManga(Request $request): Response
    {
        $manga = $this->mangaRepository->findOneBy(['slug' => $request->request->get('slug')]);
        if ($manga) {
            return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
        }

        $manga = new Manga();
        $manga->setTitle($request->request->get('title'))
            ->setSlug($request->request->get('slug'))
            ->setDescription($request->request->get('description'))
            ->setStatus($request->request->get('status'))
            ->setGenres(explode(',', $request->request->get('genres')))
            ->setAuthor($request->request->get('author'))
            ->setPublicationYear($request->request->get('publicationYear'))
            ->setRating($request->request->get('rating'))
            ->setExternalId($request->request->get('externalId'))
            ->setMonitored(false);

        // Traitement de l'image
        $imageUrl = $request->request->get('imageUrl');
        try {
            $imageUrls = $this->processAndSaveImage($imageUrl);
            $manga->setImageUrl($imageUrls['full']);
            $manga->setThumbnailUrl($imageUrls['thumbnail']);
        } catch (\Exception|GuzzleException $e) {
            throw $e;
        }

        $mergedChapters = $this->mangadexProvider->addAllChaptersToManga($manga);

        if (empty($mergedChapters)) {
            return $this->redirectToRoute('app_manga_search', ['query' => $manga->getTitle()]);
        }

        try {
            foreach ($manga->getChapters() as $chapter) {
                $this->entityManager->persist($chapter);
            }

            $this->entityManager->persist($manga);
            $this->entityManager->flush();
        } catch (\Exception $e) {
            if ($e instanceof UniqueConstraintViolationException) {
                return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
            }
            throw $e;
        }

        return $this->redirectToRoute('app_manga_show', ['mangaSlug' => $manga->getSlug()]);
    }

    /**
     * @throws GuzzleException
     */
    private function processAndSaveImage(string $imageUrl): array
    {
        $client = new Client();
        $response = $client->get($imageUrl);
        $tempImage = tmpfile();
        fwrite($tempImage, $response->getBody()->getContents());
        $tempImagePath = stream_get_meta_data($tempImage)['uri'];

        // Générer un nom de fichier unique
        $originalFilename = pathinfo($imageUrl, PATHINFO_FILENAME);
        $newFilename = $this->fileSystemManager->generateUniqueImageFilename($imageUrl);

        try {
            // Créer et sauvegarder la miniature
            $thumbnail = $this->imageManager->read($tempImagePath);
            $thumbnail->cover(300, 440);
            $thumbnail->save($this->fileSystemManager->getImagePath('thumbnails') . '/' . $newFilename, quality: 85);

            // Sauvegarder l'image en taille réelle
            $fullImage = $this->imageManager->read($tempImagePath);
            $fullImage->save($this->fileSystemManager->getImagePath('full') . '/' . $newFilename, quality: 90);

            // Fermer et supprimer le fichier temporaire
            fclose($tempImage);

            return [
                'full' => '/images/full/' . $newFilename,
                'thumbnail' => '/images/thumbnails/' . $newFilename
            ];

        } catch (FileException $e) {
            // Fermer le fichier temporaire en cas d'erreur
            fclose($tempImage);
            throw $e;
        }
    }

    #[Route('/searchChapter/{id}', name: 'search_chapter')]
    public function addChapterMessenger(int $id): JsonResponse
    {
        $chapter = $this->chapterRepository->find($id);
        if (!$chapter) {
            return new JsonResponse(['error' => 'Chapter Not Found.'], 400);
        } elseif ($chapter->getCbzPath() !== null) {
            return new JsonResponse(['error' => 'Chapter already scraped.'], 400);
        }

        $this->bus->dispatch(new DownloadChapter($id));

        return new JsonResponse(['success' => 'Scrapping started...'], 200);
    }

    #[Route('/searchVolume/{mangaSlug}/{volume}', name: 'search_volume')]
    public function searchVolume(string $mangaSlug, int $volume): JsonResponse
    {
        $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);
        if (!$manga) {
            return new JsonResponse(['error' => 'Manga Not Found.'], 400);
        }

        $volumeChapters = $this->chapterRepository->findBy([
            'manga' => $manga,
            'volume' => $volume,
            'visible' => true
        ]);

        if (empty($volumeChapters)) {
            $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'No chapters found for this volume.']);
            return new JsonResponse(['error' => 'No chapters found for this volume.'], 200);
        }

        foreach ($volumeChapters as $chapter) {
            if ($chapter->getCbzPath() === null) {
                $this->bus->dispatch(new DownloadChapter($chapter->getId()));
            }
        }

        return new JsonResponse(['success' => 'Scrapping started...'], 200);
    }

    #[Route('/download-cbz/{chapterId}', name: 'download_cbz')]
    public function downloadChapter(int $chapterId): BinaryFileResponse|JsonResponse
    {
        $chapter = $this->chapterRepository->find($chapterId);
        if (!$chapter) {
            $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Chapitre non trouvé.']);
            return new JsonResponse(['error' => 'Chapitre non trouvé.'], 200);
        }

        $cbzPath = $chapter->getCbzPath();
        if (!$cbzPath || !file_exists($cbzPath)) {
            $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Le fichier CBZ n\'existe pas.']);
            return new JsonResponse(['error' => 'Le fichier CBZ n\'existe pas.'], 200);
        }

        $isFullVolume = $this->isFullVolume($chapter);
        $fileName = $isFullVolume
            ? $this->cbzService->generateFileName($chapter->getManga(), $chapter->getVolume())
            : $this->cbzService->generateFileName($chapter->getManga(), null, $chapter->getNumber());

        return $this->cbzService->createBinaryFileResponse($cbzPath, $fileName);
    }

    #[Route('/download-volume/{mangaSlug}/{volume}', name: 'download_volume')]
    public function downloadVolume(string $mangaSlug, int $volume): BinaryFileResponse|JsonResponse
    {
        $manga = $this->mangaRepository->findOneBy(['slug' => $mangaSlug]);

        $volumeChapters = $this->chapterRepository->findBy([
            'manga' => $manga,
            'volume' => $volume,
            'visible' => true
        ], ['number' => 'ASC']);

        if (empty($volumeChapters)) {
            $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Aucun chapitre trouvé pour ce volume.']);
        }

        if (!$this->cbzService->doAllChaptersHaveCbz($volumeChapters)) {
            $this->notificationService->sendUpdate(['status' => 'error', 'message' => 'Tous les chapitres du volume ne sont pas scrapés.']);
            return new JsonResponse(['error' => 'Tous les chapitres du volume ne sont pas scrapés.'], 200);
        }

        $fileName = $this->cbzService->generateFileName($manga, $volume);

        if ($this->cbzService->areAllChaptersCbzIdentical($volumeChapters)) {
            return $this->cbzService->createBinaryFileResponse($volumeChapters[0]->getCbzPath(), $fileName);
        } else {
            $tempFile = $this->cbzService->createVolumeArchive($volumeChapters);
            $response = $this->cbzService->createBinaryFileResponse($tempFile, $fileName);
            $response->deleteFileAfterSend(true);
            return $response;
        }
    }

    #[Route('/refresh_metadata', name: 'refresh_metadata')]
    public function refreshMetadata(Request $request): JsonResponse
    {
        $mangaId = json_decode($request->getContent(), true)['mangaId'];
        $manga = $this->mangaRepository->find($mangaId);
        if (!$manga) {
            return new JsonResponse(['error' => 'Manga Not Found.'], 400);
        }
        $this->bus->dispatch(new RefreshMetadata($mangaId));

        return new JsonResponse(['success' => 'Metadata refresh started...'], 200);
    }

    #[Route('/toggle_monitored', name: 'toggle_monitored')]
    public function toogleMonitored(Request $request): JsonResponse
    {
        $id = json_decode($request->getContent(), true)['mangaId'];
        $manga = $this->mangaRepository->find($id);
        if (!$manga) {
            return new JsonResponse(['error' => 'Manga Not Found.'], 400);
        }

        $manga->setMonitored(!$manga->isMonitored());
        $this->entityManager->persist($manga);
        $this->entityManager->flush();

        return new JsonResponse(['success' => 'Monitored status updated.', 'isMonitored' => $manga->isMonitored()], 200);
    }

    private function isFullVolume(Chapter $chapter): bool
    {
        $volumeChapters = $this->chapterRepository->findBy([
            'manga' => $chapter->getManga(),
            'volume' => $chapter->getVolume()
        ]);

        $firstChapterPath = $volumeChapters[0]->getCbzPath();
        foreach ($volumeChapters as $volumeChapter) {
            if ($volumeChapter->getCbzPath() !== $firstChapterPath) {
                return false;
            }
        }

        return true;
    }
}
