<?php

namespace App\Controller;

use App\Entity\Comment;
use App\Entity\Project;
use App\Entity\Task;
use App\Form\CommentType;
use App\Form\TaskType;
use App\Service\GeoJsonManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

#[Route('/task')]
class TaskController extends AbstractController
{
    // Page de créatiom d’une tâche
    #[Route('/create', name: 'app_task_create')]
    #[IsGranted('ROLE_USER')]
    public function create(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator): Response
    {
        if (!$request->query->has('slug')) {
            $this->addFlash('warning', $translator->trans('alert.missing_project'));

            return $this->redirectToRoute('app_project');
        }

        $slug = $request->query->get('slug');

        $repository = $entityManager->getRepository(Project::class);
        $project = $repository->findOneBySlug($slug);

        if (!$project) {
            $this->addFlash('warning', $translator->trans('alert.project_not_found'));

            return $this->redirectToRoute('app_project');
        }

        $task = new Task();
        $task->setProject($project);
        $createForm = $this->createForm(TaskType::class, $task);
        $createForm->add('submit', SubmitType::class, [
            'label' => 'button.create',
        ]);

        $createForm->handleRequest($request);
        if ($createForm->isSubmitted() and $createForm->isValid()) {
            $task = $createForm->getData();

            try {
                if (!$task->hasGeojson()) {
                    $task->setGeojson('{}');
                }
                $task->setCreatedBy($this->getUser());
                $entityManager->persist($task);
                $entityManager->flush();

                $this->addFlash('success', $translator->trans('alert.task_created'));

                return $this->redirectToRoute('app_project_show', ['slug' => $slug]);
            } catch (\Exception $exception) {
                $this->addFlash('danger', $translator->trans('alert.task_not_created'));
            }
        }

        return $this->render('task/create.html.twig', [
            'project' => $project,
            'create_form' => $createForm,
        ]);
    }

    // Page spécifique à une tâche, où l’on trouve tout ce qui la concerne
    #[Route('/{slug}', name: 'app_task_show')]
    public function show(Request $request, EntityManagerInterface $entityManager, GeoJsonManager $geoJsonManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));

            if (!$request->headers->has('Referer')) {
                throw $this->createNotFoundException($translator->trans('exception.task_not_found'));
            }

            return $this->redirect($request->headers->get('Referer'));
        }

        $project = $task->getProject();
        $randomTask = $repository->findRandomByProject($project, Task::STATUS_TODO);

        $comment = new Comment();
        $commentForm = $this->createForm(CommentType::class, $comment, [
            'action' => $this->generateUrl('app_task_comment', ['slug' => $slug]),
        ]);
        $commentForm->add('submit', SubmitType::class, [
            'label' => 'button.comment',
        ]);

        // Programmation de la télécommande JOSM

        $josmCommands = [
            // Charge l’imagerie (fond OSM par défaut)
            'imagery' => [
                (($project->hasImagery() and 'http' === substr($project->getImagery(), 0, 4)) ? 'url' : 'id') => ($project->hasImagery() ? $project->getImagery() : 'osmfr'),
            ],
        ];

        if ($task->hasOsm() and !empty($task->getOsm())) {
            // Charge le XML OSM du projet danms un calque de données
            $josmCommands['import'] = [
                'new_layer' => true,
                'layer_name' => $task->getName(),
                'url' => $this->generateUrl(
                    'app_task_osm',
                    ['slug' => $task->getSlug()],
                    UrlGeneratorInterface::ABSOLUTE_URL
                ),
            ];
        }

        if ($task->hasGeojson()) {
            $geom = \geoPHP::load($task->getGeojson(), 'json');
            $bbox = $geom->getBBox();

            // Zoome sur les données et préremplit le changeset
            $josmCommands['zoom'] = [
                'bottom' => $bbox['miny'],
                'top' => $bbox['maxy'],
                'left' => $bbox['minx'],
                'right' => $bbox['maxx'],
                'changeset_comment' => sprintf('%s %s', $project->getName(), $task->getName()),
                'changeset_source' => $project->getSource(),
                'changeset_hashtags' => $project->getHashtags(),
            ];
        }

        return $this->render('task/show.html.twig', [
            'task' => $task,
            'project' => $project,
            'commentForm' => $commentForm,
            'randomTask' => $randomTask,
            'josmCommands' => json_encode($josmCommands),
        ]);
    }

    // Ajoute un commentaire à la tâche
    #[Route('/{slug}/comment', name: 'app_task_comment')]
    #[IsGranted('ROLE_USER')]
    public function comment(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));

            return $this->redirect($request->headers->get('Referer'));
        }

        $comment = new Comment();
        $comment->setCreatedBy($this->getUser());
        $comment->setTask($task);
        $commentForm = $this->createForm(CommentType::class, $comment);
        $commentForm->add('submit', SubmitType::class, [
            'label' => 'button.comment',
        ]);

        $commentForm->handleRequest($request);
        if ($commentForm->isSubmitted() and $commentForm->isValid()) {
            $comment = $commentForm->getData();

            try {
                $entityManager->persist($comment);
                $entityManager->flush();
            } catch (\Exception $exception) {
                $this->addFlash('danger', $translator->trans('alert.can_not_comment'));
            }
        }

        return $this->redirectToRoute('app_task_show', ['slug' => $slug]);
    }

    // Modifie les informations d’une tâche
    #[Route('/{slug}/update', name: 'app_task_update')]
    #[IsGranted('ROLE_USER')]
    public function update(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));

            return $this->redirect($request->headers->get('Referer'));
        }

        $updateForm = $this->createForm(TaskType::class, $task);
        $updateForm->add('submit', SubmitType::class, [
            'label' => 'button.update',
        ]);

        $updateForm->handleRequest($request);
        if ($updateForm->isSubmitted() and $updateForm->isValid()) {
            $task = $updateForm->getData();

            try {
                $entityManager->persist($task);
                $entityManager->flush();

                return $this->redirectToRoute('app_task_show', ['slug' => $slug]);
            } catch (\Exception $exception) {
                $this->addFlash('danger', $translator->trans('alert.task_not_updated'));
            }
        }

        return $this->render('task/update.html.twig', [
            'project' => $task->getProject(),
            'task' => $task,
            'update_form' => $updateForm,
        ]);
    }

    // Supprimer une tâche
    #[Route('/{slug}/remove', name: 'app_task_remove')]
    #[IsGranted('ROLE_USER')]
    public function remove(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));

            return $this->redirect($request->headers->get('Referer'));
        }

        $project = $task->getProject();

        try {
            $entityManager->remove($task);
            $entityManager->flush();

            return $this->redirectToRoute('app_project_show', ['slug' => $project->getSlug()]);
        } catch (\Exception $exception) {
            $this->addFlash('danger', $translator->trans('alert.task_not_removed'));
        }

        return $this->redirectToRoute('app_project_show', ['slug' => $slug]);
    }

    // Passe une tâche d’un état à un autre
    private function transition(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug, $transitionName): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));

            return $this->redirectToRoute('app_project');
        }

        try {
            $taskLifecycleStateMachine->apply($task, $transitionName);

            $entityManager->persist($task);

            $entityManager->flush();
        } catch (Exception $exception) {
            $this->addFlash('warning', $translator->trans('exception.task_not_updated'));
        }

        return $this->redirectToRoute('app_task_show', ['slug' => $slug]);
    }

    // Commence une tâche
    #[Route('/{slug}/start', name: 'app_task_start')]
    #[IsGranted('ROLE_USER')]
    public function start(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        return $this->transition($taskLifecycleStateMachine, $entityManager, $translator, $slug, Task::TRANSITION_START);
    }

    // Termine une tâche
    #[Route('/{slug}/finish', name: 'app_task_finish')]
    #[IsGranted('ROLE_USER')]
    public function finish(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        return $this->transition($taskLifecycleStateMachine, $entityManager, $translator, $slug, Task::TRANSITION_FINISH);
    }

    // Abandonne une tâche
    #[Route('/{slug}/cancel', name: 'app_task_cancel')]
    #[IsGranted('ROLE_USER')]
    public function cancel(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        return $this->transition($taskLifecycleStateMachine, $entityManager, $translator, $slug, Task::TRANSITION_CANCEL);
    }

    // Recommence une tâche
    #[Route('/{slug}/reset', name: 'app_task_reset')]
    #[IsGranted('ROLE_USER')]
    public function reset(WorkflowInterface $taskLifecycleStateMachine, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        return $this->transition($taskLifecycleStateMachine, $entityManager, $translator, $slug, Task::TRANSITION_RESET);
    }

    // Renvoie le geojson associé à une tâche
    #[Route('/download/{slug}.geojson', name: 'app_task_geojson')]
    #[IsGranted('ROLE_USER')]
    public function geojson(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));

            if (!$request->headers->has('Referer')) {
                throw $this->createNotFoundException($translator->trans('exception.task_not_found'));
            }

            return $this->redirect($request->headers->get('Referer'));
        }

        if (!$task->hasGeojson()) {
            return new Response('Not found', 404);
        }

        $response = JsonResponse::fromJsonString($task->getGeojson());

        $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition(
            HeaderUtils::DISPOSITION_ATTACHMENT,
            sprintf('%s.geojson', $task->getSlug())
        ));

        return $response;
    }

    // Renvoie le gpx associé ã une tâche (concrètement il s’agit juste du geojson converti automqtiquement)
    #[Route('/download/{slug}.gpx', name: 'app_task_gpx')]
    #[IsGranted('ROLE_USER')]
    public function gpx(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash('warning', $translator->trans('alert.task_not_found'));
            if (!$request->headers->has('Referer')) {
                throw $this->createNotFoundException($translator->trans('exception.task_not_found'));
            }

            return $this->redirect($request->headers->get('Referer'));
        }

        if (!$task->hasGeojson()) {
            return new Response('Not found', 404);
        }

        $geom = \geoPHP::load($task->getGeojson(), 'json');
        $gpx = $geom->out('gpx');

        // TODO On ne vérifie rien ici en partant du principe que tout se passe
        // bien. Il doit y avoir des circonstances dans lesquelles ce n’est pas
        // exactement le cas.

        $response = new Response($gpx);

        $response->headers->set('Content-Type', 'application/xml');
        $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition(
            HeaderUtils::DISPOSITION_ATTACHMENT,
            sprintf('%s.gpx', $task->getSlug())
        ));

        return $response;
    }

    // Renvoie le XML OSM associé à la tâche
    #[Route('/download/{slug}.osm', name: 'app_task_osm')]
    #[IsGranted('PUBLIC_ACCESS')]
    public function osm(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Task::class);
        $task = $repository->findOneBySlug($slug);

        if (!$task) {
            $this->addFlash(
                'warning',
                $translator->trans('alert.task_not_found')
            );

            return $this->redirect($request->headers->get('referer'));
        }

        // On est pas obligé de faire ça maid en le faisant on s’assure que
        // c’est bien du XML valide qu’on envoie.
        $xml = new \DOMDocument();
        $xml->loadXml($task->getOsm());

        $response = new Response($xml->saveXML());

        $response->headers->set('Content-Type', 'application/xml');
        $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition(
            HeaderUtils::DISPOSITION_ATTACHMENT,
            sprintf('%s.osm', $task->getSlug())
        ));

        return $response;
    }

    // Renvoie la liste des tâches du projet sous forme de CSV (ce qui devrait
    // corresponddre à ce que l’on a pu importer)
    #[Route('/download/{slug}.csv', name: 'app_task_csv')]
    #[IsGranted('ROLE_USER')]
    public function csv(Request $request, EntityManagerInterface $entityManager, TranslatorInterface $translator, $slug): Response
    {
        $repository = $entityManager->getRepository(Project::class);
        $project = $repository->findOneBySlug($slug);

        if (!$project) {
            $this->addFlash('warning', $translator->trans('alert.project_not_found'));

            return $this->redirect($request->headers->get('referer'));
        }

        $response = new StreamedResponse();

        $response->setCallback(function () use ($project): void {
            $output = fopen('php://output', 'a');
            fputcsv(
                $output, [
                    'name',
                    'description',
                    'osm',
                    'geojson',
                    'status',
                ]
            );
            foreach ($project->getTasks() as $task) {
                fputcsv(
                    $output,
                    [
                        $task->getName(),
                        $task->getDescription(),
                        $task->getOsm(),
                        $task->getGeojson(),
                        $task->getStatus(),
                    ]
                );
            }
            fclose($output);
            flush();
        });

        $response->headers->set('Content-Type', 'text/csv');
        $response->headers->set('Content-Disposition', HeaderUtils::makeDisposition(
            HeaderUtils::DISPOSITION_ATTACHMENT,
            sprintf('%s.csv', $project->getSlug())
        ));

        return $response;
    }
}
