<?php

namespace App\Controller;

use App\Controller\Attribute\MapQueryCached;
use App\Dto\SnipFilterRequest;
use App\Entity\Snip;
use App\Entity\SnipContent;
use App\Entity\Tag;
use App\Form\ConfirmationType;
use App\Form\SnipType;
use App\Repository\SnipRepository;
use App\Security\Voter\SnipVoter;
use App\Service\SnipContent\SnipContentService;
use App\Service\SnipParser\ParserFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Cache\CacheInterface;

#[Route('/snip', name: 'snip')]
class SnipController extends AbstractController
{
    public function __construct(
        private readonly SnipRepository $repository,
    ) {}

    #[Route('/', name: '_index')]
    public function index(#[MapQueryCached] SnipFilterRequest $request): Response
    {
        return $this->render('snip/index.html.twig', [
            'snips' => $this->repository->findByRequest($this->getUser(), $request),
            'request' => $request,
        ]);
    }

    #[Route('/public', name: '_public')]
    public function public(): Response
    {
        return $this->render('snip/public.html.twig', [
            'snips' => $this->repository->findPublic(),
        ]);
    }

    #[Route('/single/{snip}', name: '_single')]
    public function single(Snip $snip, ParserFactory $pf): Response
    {
        $this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);

        return $this->render('snip/single.html.twig', [
            'snip' => $snip,
            'content' => $pf->getBySnip($snip)->parseView($snip->getActiveText()),
        ]);
    }

    #[Route('/raw/{snip}', name: '_raw')]
    public function raw(Snip $snip, ParserFactory $pf, Request $request): Response
    {
        $this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);

        $response = new Response(
            $pf->getBySnip($snip)->parseRaw($snip->getActiveText()),
            Response::HTTP_OK,
            ['Content-Type' => 'text/html']
        );
        $response
            ->setVary(['Accept', 'Accept-Encoding'])
            ->setEtag(md5($response->getContent()))
            ->setTtl(3600)
            ->setClientTtl(300)
        ;

        if (!$request->isNoCache()) {
            $response->isNotModified($request);
        }

        return $response;
    }

    #[Route('/edit/{snip}', name: '_edit')]
    public function edit(Snip $snip, Request $request, SnipContentService $contentService): Response
    {
        $this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);

        /**
         * Temporary solution to prevent editing of old versions
         * It technically fully works, but rendering the version history needs an update first
         */
        $isLatest = $snip->activeVersion === $snip->getLatestVersion();
        if (!$isLatest) {
            $this->addFlash('error', 'Snip is not the latest version, changes will not be saved.');
        }

        $form = $this->createForm(SnipType::class, $snip)
            ->add('Save', SubmitType::class);
        $form->get('content')->setData($snip->getActiveText());

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            if (!$isLatest) {
                return $this->redirectToRoute('snip_single', [
                    'snip' => $snip->id,
                ]);
            }
            $this->repository->save($snip);
            $contentService->update(
                $snip,
                $form->get('content')->getData(),
                $form->get('contentName')->getData()
            );

            $this->addFlash('success', sprintf('Snip "%s" saved', $snip));

            return $this->redirectToRoute('snip_single', [
                'snip' => $snip->id,
            ]);
        }

        return $this->render('snip/edit.html.twig', [
            'snip' => $snip,
            'form' => $form->createView(),
        ]);
    }

    #[Route('/new/{cacheKey}', name: '_new')]
    public function new(
        Request $request,
        SnipContentService $contentService,
        CacheInterface $cache,
        ?string $cacheKey = null,
    ): Response
    {
        $snip = new Snip();
        $snip->setCreatedAtNow();
        $snip->createdBy = $this->getUser();

        $form = $this->createForm(SnipType::class, $snip);
        $form->add('Create', SubmitType::class);

        if ($cacheKey) {
            $content = $cache->get($cacheKey, fn() => null);
            $form->get('content')->setData($content);
            $form->get('parser')->setData('unsafe');
        }

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $this->repository->save($snip);
            $contentService->update(
                $snip,
                $form->get('content')->getData(),
                $form->get('contentName')->getData()
            );

            $this->addFlash('success', sprintf('Snip "%s" created', $snip));

            return $this->redirectToRoute('snip_single', [
                'snip' => $snip->id,
            ]);
        }

        return $this->render('snip/create.html.twig', [
            'snip' => $snip,
            'form' => $form->createView(),
        ]);
    }

    #[Route('/delete/{snip}', name: '_delete')]
    public function delete(Snip $snip, Request $request): Response
    {
        $this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);

        $form = $this->createForm(ConfirmationType::class);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $snip->activeVersion = null;
            $this->repository->save($snip);
            $this->repository->remove($snip);
            $this->addFlash('success', sprintf('Snip "%s" deleted', $snip));
            return $this->redirectToRoute('snip_index');
        }

        return $this->render('generic/form.html.twig', [
            'message' => sprintf('Do you really want to delete "%s"?', $snip),
            'form' => $form->createView(),
        ]);
    }

    #[Route('/archive/{snip}', name: '_archive')]
    public function archive(Snip $snip): Response
    {
        $this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
        $snip->archived = !$snip->archived;
        $this->repository->save($snip);
        if ($snip->archived) {
            $this->addFlash('success', sprintf('Snip "%s" archived', $snip));
        } else {
            $this->addFlash('success', sprintf('Snip "%s" unarchived', $snip));
        }

        return $this->redirectToRoute('snip_edit', ['snip' => $snip->id]);
    }
}
