<?php

namespace Mautic\DashboardBundle\Controller;

use Mautic\CoreBundle\Controller\AbstractFormController;
use Mautic\CoreBundle\Form\Type\DateRangeType;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\PathsHelper;
use Mautic\CoreBundle\Helper\PhpVersionHelper;
use Mautic\CoreBundle\Release\ThisRelease;
use Mautic\DashboardBundle\Dashboard\Widget as WidgetService;
use Mautic\DashboardBundle\Entity\Widget;
use Mautic\DashboardBundle\Form\Type\UploadType;
use Mautic\DashboardBundle\Model\DashboardModel;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;
use Twig\Environment;

class DashboardController extends AbstractFormController
{
    /**
     * Generates the default view.
     */
    public function indexAction(Request $request, WidgetService $widget, FormFactoryInterface $formFactory, PathsHelper $pathsHelper, RouterInterface $urlGenerator): Response
    {
        $model   = $this->getModel('dashboard');
        \assert($model instanceof DashboardModel);
        $widgets = $model->getWidgets();

        // Apply the default dashboard if no widget exists
        if (!count($widgets) && $this->user->getId()) {
            return $this->applyDashboardFileAction($request, $pathsHelper, $urlGenerator, 'global.default');
        }

        $action          = $this->generateUrl('mautic_dashboard_index');
        $dateRangeFilter = $request->query->all()['daterange'] ?? $request->request->all()['daterange'] ?? [];

        // Set new date range to the session
        if ($request->isMethod(Request::METHOD_POST)) {
            if (!empty($dateRangeFilter['date_from'])) {
                $from = new \DateTime($dateRangeFilter['date_from']);
                $request->getSession()->set('mautic.daterange.form.from', $from->format(DateTimeHelper::FORMAT_DB_DATE_ONLY));
            }

            if (!empty($dateRangeFilter['date_to'])) {
                $to = new \DateTime($dateRangeFilter['date_to']);
                $request->getSession()->set('mautic.daterange.form.to', $to->format(DateTimeHelper::FORMAT_DB_DATE_ONLY.' 23:59:59'));
            }

            $model->clearDashboardCache();
        }

        // Set new date range to the session, if present in POST
        $widget->setFilter($request);

        // Load date range from session
        $filter = $model->getDefaultFilter();

        // Set the final date range to the form
        $dateRangeFilter['date_from'] = $filter['dateFrom']->format(WidgetService::FORMAT_HUMAN);
        $dateRangeFilter['date_to']   = $filter['dateTo']->format(WidgetService::FORMAT_HUMAN);
        $dateRangeForm                = $formFactory->create(DateRangeType::class, $dateRangeFilter, ['action' => $action]);

        $model->populateWidgetsContent($widgets, $filter);
        $releaseMetadata = ThisRelease::getMetadata();

        $model->populateWidgetPreviews($widgets);

        return $this->delegateView([
            'viewParameters' => [
                'security'      => $this->security,
                'widgets'       => $widgets,
                'dateRangeForm' => $dateRangeForm->createView(),
                'phpVersion'    => [
                    'isOutdated' => version_compare(PHP_VERSION, $releaseMetadata->getShowPHPVersionWarningIfUnder(), 'lt'),
                    'version'    => PhpVersionHelper::getCurrentSemver(),
                ],
            ],
            'contentTemplate' => '@MauticDashboard/Dashboard/index.html.twig',
            'passthroughVars' => [
                'activeLink'    => '#mautic_dashboard_index',
                'mauticContent' => 'dashboard',
                'route'         => $this->generateUrl('mautic_dashboard_index'),
            ],
        ]);
    }

    public function widgetAction(Request $request, WidgetService $widgetService, Environment $twig, $widgetId): JsonResponse
    {
        if (!$request->isXmlHttpRequest()) {
            throw new NotFoundHttpException('Not found.');
        }

        $widgetService->setFilter($request);
        $widget        = $widgetService->get((int) $widgetId);

        if (!$widget) {
            throw new NotFoundHttpException('Not found.');
        }

        $content = $twig->render(
            '@MauticDashboard/Dashboard/widget.html.twig',
            ['widget' => $widget]
        );

        return new JsonResponse([
            'success'      => 1,
            'widgetId'     => $widgetId,
            'widgetHtml'   => $content,
            'widgetWidth'  => $widget->getWidth(),
            'widgetHeight' => $widget->getHeight(),
        ]);
    }

    /**
     * Generate new dashboard widget and processes post data.
     *
     * @return JsonResponse|RedirectResponse|Response
     */
    public function newAction(Request $request, FormFactoryInterface $formFactory)
    {
        // retrieve the entity
        $widget = new Widget();

        $model  = $this->getModel('dashboard');
        \assert($model instanceof DashboardModel);
        $action = $this->generateUrl('mautic_dashboard_action', ['objectAction' => 'new']);

        // get the user form factory
        $form       = $model->createForm($widget, $formFactory, $action);
        $closeModal = false;
        $valid      = false;

        // /Check for a submitted form and process it
        if ($request->isMethod(Request::METHOD_POST)) {
            if (!$cancelled = $this->isFormCancelled($form)) {
                if ($valid = $this->isFormValid($form)) {
                    $closeModal = true;

                    // form is valid so process the data
                    $model->saveEntity($widget);
                }
            } else {
                $closeModal = true;
            }
        }

        if ($closeModal) {
            // just close the modal
            $passthroughVars = [
                'closeModal'    => 1,
                'mauticContent' => 'widget',
            ];

            $filter = $model->getDefaultFilter();
            $model->populateWidgetContent($widget, $filter);

            if ($valid && !$cancelled) {
                $passthroughVars['upWidgetCount'] = 1;
                $passthroughVars['widgetHtml']    = $this->renderView('@MauticDashboard/Widget/detail.html.twig', [
                    'widget' => $widget,
                ]);
                $passthroughVars['widgetId']     = $widget->getId();
                $passthroughVars['widgetWidth']  = $widget->getWidth();
                $passthroughVars['widgetHeight'] = $widget->getHeight();
                $this->addFlashMessage('mautic.dashboard.widget.created');
            }
            $passthroughVars['flashes'] = $this->getFlashContent();

            return new JsonResponse($passthroughVars);
        } else {
            return $this->delegateView([
                'viewParameters' => [
                    'form' => $form->createView(),
                ],
                'contentTemplate' => '@MauticDashboard/Widget/form.html.twig',
            ]);
        }
    }

    /**
     * edit widget and processes post data.
     *
     * @return JsonResponse|RedirectResponse|Response
     */
    public function editAction(Request $request, FormFactoryInterface $formFactory, $objectId)
    {
        $model  = $this->getModel('dashboard');
        \assert($model instanceof DashboardModel);
        $widget = $model->getEntity($objectId);
        $action = $this->generateUrl('mautic_dashboard_action', ['objectAction' => 'edit', 'objectId' => $objectId]);

        // get the user form factory
        $form       = $model->createForm($widget, $formFactory, $action);
        $closeModal = false;
        $valid      = false;
        // /Check for a submitted form and process it
        if ($request->isMethod(Request::METHOD_POST)) {
            if (!$cancelled = $this->isFormCancelled($form)) {
                if ($valid = $this->isFormValid($form)) {
                    $closeModal = true;

                    // form is valid so process the data
                    $model->saveEntity($widget);
                }
            } else {
                $closeModal = true;
            }
        }

        if ($closeModal) {
            // just close the modal
            $passthroughVars = [
                'closeModal'    => 1,
                'mauticContent' => 'widget',
            ];

            $filter = $model->getDefaultFilter();
            $model->populateWidgetContent($widget, $filter);

            if ($valid && !$cancelled) {
                $passthroughVars['upWidgetCount'] = 1;
                $passthroughVars['widgetHtml']    = $this->renderView('@MauticDashboard/Widget/detail.html.twig', [
                    'widget' => $widget,
                ]);
                $passthroughVars['widgetId']     = $widget->getId();
                $passthroughVars['widgetWidth']  = $widget->getWidth();
                $passthroughVars['widgetHeight'] = $widget->getHeight();
            }

            return new JsonResponse($passthroughVars);
        } else {
            return $this->delegateView([
                'viewParameters' => [
                    'form' => $form->createView(),
                ],
                'contentTemplate' => '@MauticDashboard/Widget/form.html.twig',
            ]);
        }
    }

    /**
     * Deletes entity if exists.
     *
     * @param int $objectId
     */
    public function deleteAction(Request $request, $objectId): Response
    {
        if (!$request->isXmlHttpRequest()) {
            throw new BadRequestHttpException();
        }

        $flashes = [];
        $success = 0;

        /** @var DashboardModel $model */
        $model  = $this->getModel('dashboard');
        $entity = $model->getEntity($objectId);

        if ($entity) {
            $model->deleteEntity($entity);
            $name      = $entity->getName();
            $flashes[] = [
                'type'    => 'notice',
                'msg'     => 'mautic.core.notice.deleted',
                'msgVars' => [
                    '%name%' => $name,
                    '%id%'   => $objectId,
                ],
            ];
            $success = 1;
        } else {
            $flashes[] = [
                'type'    => 'error',
                'msg'     => 'mautic.api.client.error.notfound',
                'msgVars' => ['%id%' => $objectId],
            ];
        }

        return $this->postActionRedirect(
            [
                'success' => $success,
                'flashes' => $flashes,
            ]
        );
    }

    /**
     * Saves the widgets of current user into a json and stores it for later as a file.
     *
     * @return Response
     */
    public function saveAction(Request $request)
    {
        // Accept only AJAX POST requests because those are check for CSRF tokens
        if (!$request->isMethod(Request::METHOD_POST) || !$request->isXmlHttpRequest()) {
            return $this->accessDenied();
        }

        $name = $this->getNameFromRequest($request);

        /** @var DashboardModel $dashboardModel */
        $dashboardModel = $this->getModel('dashboard');
        try {
            $dashboardModel->saveSnapshot($name);
            $type = 'notice';
            $msg  = $this->translator->trans('mautic.dashboard.notice.save', [
                '%name%'    => $name,
                '%viewUrl%' => $this->generateUrl(
                    'mautic_dashboard_action',
                    [
                        'objectAction' => 'import',
                    ]
                ),
            ], 'flashes');
        } catch (IOException $e) {
            $type = 'error';
            $msg  = $this->translator->trans('mautic.dashboard.error.save', [
                '%msg%' => $e->getMessage(),
            ], 'flashes');
        }

        return $this->postActionRedirect(
            [
                'flashes' => [
                    [
                        'type' => $type,
                        'msg'  => $msg,
                    ],
                ],
            ]
        );
    }

    /**
     * Exports the widgets of current user into a json file and downloads it.
     */
    public function exportAction(Request $request): JsonResponse
    {
        $dashboardModel = $this->getModel('dashboard');
        \assert($dashboardModel instanceof DashboardModel);
        $filename = InputHelper::filename($this->getNameFromRequest($request), 'json');
        $response = new JsonResponse($dashboardModel->toArray($filename));
        $response->setEncodingOptions($response->getEncodingOptions() | JSON_PRETTY_PRINT);
        $response->headers->set('Content-Type', 'application/force-download');
        $response->headers->set('Content-Type', 'application/octet-stream');
        $response->headers->set('Content-Disposition', 'attachment; filename="'.$filename.'"');
        $response->headers->set('Expires', '0');
        $response->headers->set('Cache-Control', 'must-revalidate');
        $response->headers->set('Pragma', 'public');

        return $response;
    }

    /**
     * Exports the widgets of current user into a json file.
     */
    public function deleteDashboardFileAction(Request $request, PathsHelper $pathsHelper): RedirectResponse
    {
        $file = $request->get('file');

        $parts = explode('.', $file);
        $type  = array_shift($parts);
        $name  = implode('.', $parts);

        $dir  = $pathsHelper->getSystemPath("dashboard.$type");
        $path = $dir.'/'.$name.'.json';

        if (file_exists($path) && is_writable($path)) {
            unlink($path);
        }

        return $this->redirectToRoute('mautic_dashboard_action', ['objectAction' => 'import']);
    }

    /**
     * Applies dashboard layout.
     *
     * @param string|null $file
     */
    public function applyDashboardFileAction(Request $request, PathsHelper $pathsHelper, RouterInterface $urlGenerator, $file = null): RedirectResponse
    {
        if (!$file) {
            $file = $request->get('file');
        }

        $parts = explode('.', $file);
        $type  = array_shift($parts);
        $name  = implode('.', $parts);

        $dir  = $pathsHelper->getSystemPath("dashboard.$type");
        $path = $dir.'/'.$name.'.json';

        if (!file_exists($path) || !is_readable($path)) {
            $this->addFlashMessage('mautic.dashboard.upload.filenotfound', [], 'error', 'validators');

            return $this->redirectToRoute('mautic_dashboard_action', ['objectAction' => 'import']);
        }

        $widgets = json_decode(file_get_contents($path), true);
        if (isset($widgets['widgets'])) {
            $widgets = $widgets['widgets'];
        }

        if ($widgets) {
            /** @var DashboardModel $model */
            $model = $this->getModel('dashboard');

            $model->clearDashboardCache();

            $currentWidgets = $model->getWidgets();

            if (count($currentWidgets)) {
                foreach ($currentWidgets as $widget) {
                    $model->deleteEntity($widget);
                }
            }

            $filter = $model->getDefaultFilter();
            foreach ($widgets as $widget) {
                $widget = $model->populateWidgetEntity($widget);
                $model->saveEntity($widget);
            }
        }

        return $this->redirect($urlGenerator->generate('mautic_dashboard_index'));
    }

    public function importAction(Request $request, FormFactoryInterface $formFactory, PathsHelper $pathsHelper): Response
    {
        $preview = $request->get('preview');

        /** @var DashboardModel $model */
        $model = $this->getModel('dashboard');

        $directories = [
            'user'   => $pathsHelper->getSystemPath('dashboard.user'),
            'global' => $pathsHelper->getSystemPath('dashboard.global'),
        ];

        $action = $this->generateUrl('mautic_dashboard_action', ['objectAction' => 'import']);
        $form   = $formFactory->create(UploadType::class, [], ['action' => $action]);

        if ($request->isMethod(Request::METHOD_POST)) {
            if (!$this->isFormCancelled($form)) {
                if ($this->isFormValid($form)) {
                    $fileData = $form['file']->getData();
                    if (!empty($fileData)) {
                        $extension = pathinfo($fileData->getClientOriginalName(), PATHINFO_EXTENSION);
                        if ('json' === $extension) {
                            $fileData->move($directories['user'], $fileData->getClientOriginalName());
                        } else {
                            $form->addError(
                                new FormError(
                                    $this->translator->trans('mautic.core.not.allowed.file.extension', ['%extension%' => $extension], 'validators')
                                )
                            );
                        }
                    } else {
                        $form->addError(
                            new FormError(
                                $this->translator->trans('mautic.dashboard.upload.filenotfound', [], 'validators')
                            )
                        );
                    }
                }
            }
        }

        $dashboardFiles = ['user' => [], 'gobal' => []];
        $dashboards     = [];

        if (is_readable($directories['user'])) {
            // User specific layouts
            chdir($directories['user']);
            $dashboardFiles['user'] = glob('*.json');
        }

        if (is_readable($directories['global'])) {
            // Global dashboards
            chdir($directories['global']);
            $dashboardFiles['global'] = glob('*.json');
        }

        foreach ($dashboardFiles as $type => $dirDashboardFiles) {
            $tempDashboard = [];
            foreach ($dirDashboardFiles as $dashId => $dashboard) {
                $dashboard = str_replace('.json', '', $dashboard);
                $config    = json_decode(
                    file_get_contents($directories[$type].'/'.$dirDashboardFiles[$dashId]),
                    true
                );

                // Check for name, description, etc
                $tempDashboard[$dashboard] = [
                    'type'        => $type,
                    'name'        => $config['name'] ?? $dashboard,
                    'description' => $config['description'] ?? '',
                    'widgets'     => $config['widgets'] ?? $config,
                ];
            }

            // Sort by name
            uasort($tempDashboard,
                fn ($a, $b): int => strnatcasecmp($a['name'], $b['name'])
            );

            $dashboards = array_merge(
                $dashboards,
                $tempDashboard
            );
        }

        if ($preview && isset($dashboards[$preview])) {
            // @todo check is_writable
            $widgets = $dashboards[$preview]['widgets'];
            $filter  = $model->getDefaultFilter();
            $model->populateWidgetsContent($widgets, $filter);
        } else {
            $widgets = [];
        }

        return $this->delegateView(
            [
                'viewParameters' => [
                    'form'       => $form->createView(),
                    'dashboards' => $dashboards,
                    'widgets'    => $widgets,
                    'preview'    => $preview,
                ],
                'contentTemplate' => '@MauticDashboard/Dashboard/import.html.twig',
                'passthroughVars' => [
                    'activeLink'    => '#mautic_dashboard_index',
                    'mauticContent' => 'dashboardImport',
                    'route'         => $this->generateUrl(
                        'mautic_dashboard_action',
                        [
                            'objectAction' => 'import',
                        ]
                    ),
                ],
            ]
        );
    }

    /**
     * Gets name from request and defaults it to the timestamp if not provided.
     *
     * @throws \Exception
     */
    private function getNameFromRequest(Request $request): string
    {
        return $request->get('name', (new \DateTime())->format('Y-m-dTH:i:s'));
    }
}
