<?php

namespace Webkul\ScheduleCronBundle\Controller\Rest;

use  Cron\CronExpression;
use Oro\Bundle\SecurityBundle\Annotation\AclAncestor;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Validator\Constraints\NotBlank;
use Webkul\ScheduleCronBundle\Services\ScheduleCronConnector;

/**
 * Configuration rest controller in charge of the schedule cron connector configuration managements
 */
class ConfigurationController extends AbstractController
{
    public const MAIN_LOCALE = 'en';

    protected $username = null;

    public function __construct(
        public ScheduleCronConnector $connectorService,
        ContainerInterface $container,
        private \Doctrine\ORM\EntityManager $em
    ) {}

    /**
     * Get the current configuration
     * @AclAncestor("webkul_schedulecron_connector_configuration")
     *
     * @return JsonResponse
     */
    public function getAction()
    {
        $data = [];
        $output = shell_exec('crontab -l');
        $datas = $this->connectorService->getData();

        foreach ($datas as $value) {
            $data[] = $this->getAllData($value);
        }

        if ($datas) {
            $this->checkjob();
        }

        return new JsonResponse($data);
    }

    public function getActionbyId(Request $request, $id)
    {
        $data = [];
        $mapping = $this->em->getRepository('ScheduleCronBundle:CronJobsMapping')->find($id);

        if ($mapping) {
            $data = [
               'id'             => $mapping->getId(),
               'name'           => $mapping->getName(),
               'intervelday'    => $mapping->getIntervelDay(),
               'interveltime'   => $mapping->getIntervelTime(),
               'custom_cron'    => $mapping->getCustomCron(),
               'job'            => $mapping->getJobProfile(),
               'status'         => $mapping->getStatus(),
               'jobType'        => $mapping->getjobType(),
            ];
        }

        return new JsonResponse($data);
    }

    /**
     * Set the current configuration
     * @AclAncestor("webkul_schedulecron_connector_configuration")
     * @return JsonResponse
     */
    public function postAction(Request $request)
    {
        $data  = [];
        $this->username = $this->get('security.token_storage')->getToken()->getUsername();
        $param = $request->request->all();

        if ($request->getMethod() == 'POST') {
            $form = $this->getConfigForm($param);
            $form->submit($param);

            if ($form->isValid()) {
                $customCronCheck = $param['check_custom_cron'] ?? false;
                $param['cronCmd'] = $this->editCronMethod($param);
                $cronValue = isset($param['cronCmd']['error']) ? '' : substr($param['cronCmd'], 0, strpos($param['cronCmd'], ' cd '));
                $validateCron = $this->isCronValid($customCronCheck === true ? $param['custom_cron'] : $cronValue);
                if ($param['cronCmd'] && !isset($param['cronCmd']['error']) && $validateCron === true) {
                    $this->connectorService->saveData($param);
                } else {
                    $message = $validateCron->getMessage();
                    $error = ["custom_cron" => $message ];

                    return new JsonResponse($error, Response::HTTP_BAD_REQUEST);
                }
            } else {
                return new JsonResponse($this->getFormErrors($form), Response::HTTP_BAD_REQUEST);
            }
        }

        return $this->getAction();
    }

    /**
     * This function updated the cron value into database.
     */
    public function UpdateAction(Request $request)
    {
        $this->username = $this->get('security.token_storage')->getToken()->getUsername();
        $param = $request->request->all();
        $updateId = $param['id'];
        $mapping = $this->em->getRepository('ScheduleCronBundle:CronJobsMapping')->find($updateId);
        $this->deleteTerminalCronJob($mapping->getCronCmd());

        if ($request->getMethod() == 'POST') {
            // Check if the provided ID exists
            $existingEntity = $this->em->getRepository('ScheduleCronBundle:CronJobsMapping')->find($updateId);
            if ($existingEntity) {
                $form = $this->getConfigForm($param);
                $form->submit($param);
                if ($form->isValid()) {
                    $customCronCheck = $param['check_custom_cron'] ?? false;
                    $customcron = "N/A";
                    $intervelday = "N/A";
                    $interveltime = "N/A";
                    $param['cronCmd'] = $this->editCronMethod($param);
                    $cronValue = isset($param['cronCmd']['error']) ? '' : substr($param['cronCmd'], 0, strpos($param['cronCmd'], ' cd '));
                    $validateCron = $this->isCronValid($customCronCheck === true ? $param['custom_cron'] : $cronValue);

                    if ($validateCron === true) {
                        if ($customCronCheck === "true") {
                            $customcron = isset($param['custom_cron']) ? str_replace('_', ' ', $param['custom_cron']) : null;
                            $existingEntity->setCronCmd($param['cronCmd']);
                        } else {
                            $intervelday = $param['intervelday'] ?? null;
                            $interveltime = $param['interveltime'] ?? null;
                            $existingEntity->setCronCmd($param['cronCmd']);
                        }
                        $status = ($existingEntity->getStatus() == false) ? '0' : '1';
                        $existingEntity->setName($param['name']);
                        $existingEntity->setIntervelDay($intervelday);
                        $existingEntity->setIntervelTime($interveltime);
                        $existingEntity->setCustomCron($customcron);
                        $existingEntity->setJobProfile($param['job']);
                        $existingEntity->setStatus($status);
                        $existingEntity->setjobType($param['jobType']);
                        $this->em->flush();
                    } else {
                        $message = $validateCron->getMessage();
                        $error = ["custom_cron" => $message ];

                        return new JsonResponse($error, Response::HTTP_BAD_REQUEST);
                    }
                } else {
                    $error = $this->getFormErrors($form);
                    return new JsonResponse($this->getFormErrors($form), Response::HTTP_BAD_REQUEST);
                }
            } else {
                $error = ["message" => "The provided ID does not exist"];

                return new JsonResponse($error, Response::HTTP_NOT_FOUND);
            }
        }

        return $this->getAction();
    }

    public function checkjob()
    {
        $output = shell_exec('crontab -l');
        $cronJobLines = explode("\n", $output); // Split the configuration into lines
        $dat = $this->connectorService->getData();

        foreach ($dat as $value) {
            $data[] = $this->getData($value);
        }

        foreach ($cronJobLines as $line) {
            $trimmedLine = trim($line);
            foreach($data as $key => $value) {
                $idFromDB = $value['id'];

                if (!empty($trimmedLine) && $trimmedLine[0] === '#') {
                    if (strpos($trimmedLine, "akeneo:batch:publish-job-to-queue") !== false) {
                        $crontab = trim(str_replace('#', '', $trimmedLine));
                        if ($crontab == $value['cronCmd']) {
                            $cronJobEnabled = false;
                            $this->toogleActionViaTerminal($idFromDB, $cronJobEnabled);
                        }
                    }
                } else {
                    if (strpos($trimmedLine, "akeneo:batch:publish-job-to-queue") !== false) {
                        $crontab = trim($trimmedLine);
                        if($crontab == $value['cronCmd']) {
                            $cronJobEnabled = true;
                            $this->toogleActionViaTerminal($idFromDB, $cronJobEnabled);
                        }
                    }
                }
            }
        }
    }

    /**
     * Add Cron Command to crontab
     * @param $params
     * @return commandLine $line | null
     */
    public function editCronMethod($params)
    {
        $customCron = isset($params['check_custom_cron']) ? $params['check_custom_cron'] : false;
        $line = null;
        if ($customCron === 'true') {
            $validateCron = $this->isCronValid($params['custom_cron']);

            if ($validateCron === true) {
                $line = $this->addCustomCron($params);
            } else {
                return;
            }
        } else {
            $intervelDay = $params['intervelday'];

            switch ($intervelDay) {
                case "Daily at":
                    $line = $this->changeDailyBase($params);
                    break;
                case "Hourly":
                    $line = $this->changeHourlyBase($params);
                    break;
                case "Weekly":
                    $line = $this->changeWeeklyBase($params);
                    break;
                case "Monthly":
                    $line = $this->changeMonthlyBase($params);
                    break;
                case "By Minute":
                    $line = $this->changeMinuteBase($params);
                    break;
                default:
                    die("default");
            }
        }

        $output = shell_exec('crontab -l');
        $directory = $this->connectorService->getRootPath();
        $directory = $directory . '/var/logs/crontab.txt';

        if (!(strpos($output, $line))) {
            file_put_contents($directory, $output . $line . PHP_EOL);

            $execu = 'crontab ' . $directory;
            $error = exec($execu . ' 2>&1');

            if ($error) {
                $result['error'] = $error;

                return $result;
            }
        }

        $realoutput = shell_exec('crontab -l');
        if ($realoutput) {
            return $line;
        }

        return null;
    }

    public function isCronValid($expression)
    {
        try {
            CronExpression::factory($expression);
            return true;
        } catch (\InvalidArgumentException $e) {
            return $e;
        }
    }

    /**
     * Create Custom Cron Command
     * @param $params
     * @return string
     */
    public function addCustomCron($params)
    {
        $h = str_replace('_', ' ', $params['custom_cron']) ;
        $prefixString = $h;

        return $this->getCronCommandLine($params, $prefixString);
    }

    /**
     * Create Daily Base Cron Command
     * @param $params
     * @return string
     */
    public function changeDailyBase($params)
    {
        $interveltime = $params['interveltime'];
        $h = $this->time[$interveltime];
        $prefixString = "0 " . $h . " * * *";

        return $this->getCronCommandLine($params, $prefixString);
    }

    /**
     * Get array data from Object
     * @param $values
     * @return Array $value
     */
    public function getAllData($values)
    {
        $value['id'] = $values->getId();
        $value['name'] = $values->getName();
        $value['status'] = $values->getStatus();
        $value['intervelDay'] = $values->getIntervelDay();
        $value['intervelTime'] = $values->getIntervelTime();
        $value['jobProfile'] = $values->getJobProfile();

        return $value;
    }

    /**
     * Get array data from Object
     * @param $values
     * @return Array $value
     */
    public function getData($values)
    {
        $value = [];
        $value['id'] = $values->getId();
        $value['name'] = $values->getName();
        $value['status'] = $values->getStatus();
        $value['intervelDay'] = $values->getIntervelDay();
        $value['intervelTime'] = $values->getIntervelTime();
        $value['jobProfile'] = $values->getJobProfile();
        $value['cronCmd'] = $values->getCronCmd();

        return $value;
    }

    /**
     * Create Hourly Base Cron Command
     * @param $params
     * @return string
     */
    public function changeHourlyBase($params)
    {
        $interveltime = $params['interveltime'];
        $h = $this->hour[$interveltime];
        $prefixString = "0 */" . $h . " * * *";

        return $this->getCronCommandLine($params, $prefixString);
    }

    /**
     * Create Monthly Base Cron Command
     * @param $params
     * @return string
     */
    public function changeMonthlyBase($params)
    {
        $interveltime = $params['interveltime'];
        $h = $this->month[$interveltime];
        $prefixString = "0 0 " . $h . " * *";

        return $this->getCronCommandLine($params, $prefixString);
    }

    /**
     * Create Weekly Base Cron Command
     * @param $params
     * @return string
     */
    public function changeWeeklyBase($params)
    {
        $interveltime = $params['interveltime'];
        $h = $this->week[$interveltime];
        $prefixString = "0 0 * * " . $h;

        return $this->getCronCommandLine($params, $prefixString);
    }

    /**
     * Create Minute Base Cron Command
     * @param $params
     * @return string
     */
    public function changeMinuteBase($params)
    {
        $interveltime = $params['interveltime'];
        $h = $this->minute[$interveltime];
        $prefixString = "*/" . $h . " * * * *";

        return $this->getCronCommandLine($params, $prefixString);
    }

    /**
     * Create Cron Command Line
     * @param $params
     * @param $prefixString
     * @return string
     */
    public function getCronCommandLine($params, $prefixString)
    {
        $jobProfile = $params['job'];
        $jobtype = $params['jobType'];

        if ($jobtype == 'queue') {
            $job = 'akeneo:batch:publish-job-to-queue';
        } else {
            $job = 'akeneo:batch:job';
        }

        $currentDirectory = __DIR__;

        if (strpos($currentDirectory, "vendor/webkul/schedulecronbundle") !== false) {
            $currentDirectory = str_replace("vendor/webkul/schedulecronbundle/src/Webkul/ScheduleCronBundle/Controller/Rest", '', $currentDirectory);
        } else {
            $currentDirectory = str_replace("src/Webkul/ScheduleCronBundle/Controller/Rest", '', $currentDirectory);
        }

        $phpVersion = shell_exec('which php') ?? 'php';
        $phpVersion = trim($phpVersion);

        $directory = $this->connectorService->getRootPath();
        $logDirectory = $directory . '/var/logs/cron.log';
        $env = $this->container->getParameter('kernel.environment');
        $consoleDirectory = "app";
        $version = Kernel::VERSION;

        if ($version >= '3.0') {
            $consoleDirectory = "bin";
        }

        $croncommandLine = $prefixString . " cd " . $currentDirectory . " && date >> " . $logDirectory . " && " . $phpVersion . " -d memory_limit=4G " . $consoleDirectory . "/console " . $job . " --username=" . $this->username . " " . $jobProfile . " --env=" . $env . " >> " . $logDirectory . ";";

        return $croncommandLine;
    }

    /**
     * Delete Command Line
     * @param $id
     * @return Response
     */
    public function deleteAction($id)
    {
        $mapping = $this->em->getRepository('ScheduleCronBundle:CronJobsMapping')->find($id);

        if (!$mapping) {
            throw new NotFoundHttpException(
                sprintf('Instance with id "%s" not found', $id)
            );
        }
        $this->deleteCronJob($mapping);
        $this->em->remove($mapping);
        $this->em->flush();

        return new Response(null, Response::HTTP_NO_CONTENT);
    }

    /** Change Status of Cron Command
     * @param $id
     * @return JsonResponse
     */
    public function toogleAction($id)
    {
        try {
            $config = $this->em->getRepository('ScheduleCronBundle:CronJobsMapping')->findOneBy([
                'id' => $id
            ]) ;

            if (!empty($config)) {
                //change current active value
                if ($this->toggleCronJob($config)) {
                    $config->isActivate();
                    $this->em->persist($config);
                    $this->em->flush();
                }
            }
        } catch (\Exception $e) {
            return new JsonResponse(['successful' => false]);
        }

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

    /** Change Status of Cron Command using crontab cli
     * @param $id
     * @return JsonResponse
     */
    public function toogleActionViaTerminal($id, $status)
    {
        try {
            $config = $this->em->getRepository('ScheduleCronBundle:CronJobsMapping')->findOneBy([
                'id' => $id
            ]) ;

            if (!empty($config)) {
                $config->isEnabled($status);
                $this->em->persist($config);
                $this->em->flush();
            }
        } catch (\Exception $e) {
            return new JsonResponse(['successful' => false]);
        }

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

    /**
     * Delete Cron Job
     * @param $mapping
     * @return string
     */
    public function deleteCronJob($mapping)
    {
        $cronString = $mapping->getCronCmd();
        $output = shell_exec('crontab -l');
        $directory = $this->connectorService->getRootPath();
        $directory = $directory . '/var/logs/crontab.txt';

        if (strpos($output, $cronString) !== false) {
            $line = str_replace($cronString, "", $output);
            file_put_contents($directory, $line . PHP_EOL);
            $execu = 'crontab ' . $directory;
            echo exec($execu);
        }
        $realoutput = shell_exec('crontab -l');

        return $realoutput;
    }

    /**
     * Delete Cron Job from crontab cli
     * @param $mapping
     * @return string
     */
    public function deleteTerminalCronJob($mapping)
    {
        $cronString = $mapping;
        $output = shell_exec('crontab -l');
        $directory = $this->connectorService->getRootPath();
        $directory = $directory . '/var/logs/crontab.txt';

        if (strpos($output, $cronString) !== false) {
            $line = str_replace($cronString, "", $output);
            file_put_contents($directory, $line . PHP_EOL);
            $execu = 'crontab ' . $directory;
            echo exec($execu);
        }
        $realoutput = shell_exec('crontab -l');

        return $realoutput;
    }

    /**
     * Change Cron Job Status
     * @param $config
     * @return string
     */
    public function toggleCronJob($config)
    {
        $cronString = $config->getCronCmd();
        $status = $config->getStatus();
        $directory = $this->connectorService->getRootPath();
        $directory = $directory . '/var/logs/crontab.txt';
        $realoutput = null;

        if ($status == '1') {
            $newCronString = "# " . $cronString;
            $output = shell_exec('crontab -l');

            if (strpos($output, $cronString) !== false) {
                $line = str_replace($cronString, $newCronString, $output);
                file_put_contents($directory, $line . PHP_EOL);
                $execu = 'crontab ' . $directory;
                echo exec($execu);
            }
            $realoutput = shell_exec('crontab -l');
        } else {
            $output = shell_exec('crontab -l');
            $searchfor = $cronString;
            // the following line prevents the browser from parsing this as HTML.
            header('Content-Type: text/plain');
            // escape special characters in the query
            $pattern = preg_quote($searchfor, '/');
            // finalise the regular expression, matching the whole line
            $pattern = "/^.*$pattern.*\$/m";
            // search, and store all matching occurences in $matches
            if (preg_match_all($pattern, $output, $matches)) {
                $newCronString = implode(",", $matches[0]);

                if (strpos($output, $cronString) !== false) {
                    $line = str_replace($newCronString, $cronString, $output);
                    file_put_contents($directory, $line . PHP_EOL);
                    $execu = 'crontab ' . $directory;
                    echo exec($execu);
                }
                $realoutput = shell_exec('crontab -l');
            }
        }

        return $realoutput;
    }

    /**
     * Get Configuration Form
     * @param $params
     * @return
     */
    private function getConfigForm($params)
    {
        $customCron = isset($params['check_custom_cron']) ? $params['check_custom_cron'] : false;

        $form = $this->createFormBuilder(null, [
                    'allow_extra_fields' => true,
                    'csrf_protection' => false
                ]);
        $form->add('name', null, [
            'constraints' => [
                new NotBlank()
            ]
        ]);

        if ($customCron === 'true') {
            $form->add('custom_cron', null, [
                'constraints' => [
                    new NotBlank()
                ]
            ]);
        } else {
            $form->add('intervelday', null, [
                'constraints' => [
                    new NotBlank()
                ]
            ]);
            $form->add('interveltime', null, [
                'constraints' => [
                    new NotBlank()
                ]
            ]);
        }
        $form->add('job', null, [
            'constraints' => [
                new NotBlank()
            ]
        ]);

        return $form->getForm();
    }

    /**
     * Get Form Errors
     * @param $params
     * @return
     */
    private function getFormErrors($form)
    {
        $errorContext = [];

        foreach ($form->getErrors(true) as $key => $error) {
            $errorContext[$error->getOrigin()->getName()] = $error->getMessage();
        }

        return $errorContext;
    }

    /**
     * Get Job Names
     * @param $params
     * @return JsonResponse
     */
    public function getJobNamesAction()
    {
        $alljob = [];
        $jobInstance = $this->get('pim_enrich.repository.job_instance');
        $qb = $jobInstance->createDatagridQueryBuilder()
                ->where('j.type IN (:jobTypes)')
                ->setParameter('jobTypes', ['export', 'import'])
                ->Select('j.code as jobCode')
                ->addSelect('j.label as jobLabel');
        $result = $qb->getQuery()->getResult();

        foreach ($result as $jobProfile) {
            $alljob[$jobProfile['jobCode']] = $jobProfile['jobLabel'];
        }
        $jobs = (object)$alljob;

        return new JsonResponse($jobs);
    }

    /**
     * hourly time
     */
    protected $time = [
        "1:00" => "1",
        "2:00" => "2",
        "3:00" => "3",
        "4:00" => "4",
        "5:00" => "5",
        "6:00" => "6",
        "7:00" => "7",
        "8:00" => "8",
        "9:00" => "9",
        "10:00" => "10",
        "11:00" => "11",
        "12:00" => "12",
        "13:00" => "13",
        "14:00" => "14",
        "15:00" => "15",
        "16:00" => "16",
        "17:00" => "17",
        "18:00" => "18",
        "19:00" => "19",
        "20:00" => "20",
        "21:00" => "21",
        "22:00" => "22",
        "23:00" => "23",
        "24:00" => "0",
    ];

    /**
     * Every hour time
     */
    protected $hour = [
        'Every 1 hour'     => '1',
        'Every 2 hour'     => '2',
        'Every 3 Hour'     => '3',
        'Every 4 Hour'     => '4',
        'Every 5 Hour'     => '5',
        'Every 6 Hour'     => '6',
        'Every 7 Hour'     => '7',
        'Every 8 Hour'     => '8',
        'Every 9 Hour'     => '9',
        'Every 10 Hour'    => '10',
        'Every 11 Hour'    => '11',
        'Every 12 Hour'    => '12',
    ];

    /**
     * Week based
     */
    protected $week = [
        'Monday'        => 'MON',
        'Tuesday'       => 'TUE',
        'Wednessday'    => 'WED',
        'Thursday'      => 'THU',
        'Friday'        => 'FRI',
        'Saturday'      => 'SAT',
        'Sunday'        => 'SUN',
    ];

    /**
     * month based
     */
    protected $month = [
        '1st Day of Month'     =>   '1',
        '2nd Day of Month'     =>   '2',
        '3rd Day of Month'     =>   '3',
        '4th Day of Month'     =>   '4',
        '5th Day of Month'     =>   '5',
        '6th Day of Month'     =>   '6',
        '7th Day of Month'     =>   '7',
        '8th Day of Month'     =>   '8',
        '9th Day of Month'     =>   '9',
        '10th Day of Month'    =>   '10',
        '11th Day of Month'    =>   '11',
        '12th Day of Month'    =>   '12',
        '13th Day of Month'    =>   '13',
        '14th Day of Month'    =>   '14',
        '15th Day of Month'    =>   '15',
        '16th Day of Month'    =>   '16',
        '17th Day of Month'    =>   '17',
        '18th Day of Month'    =>   '18',
        '19th Day of Month'    =>   '19',
        '20th Day of Month'    =>   '20',
        '21th Day of Month'    =>   '21',
        '22nd Day of Month'    =>   '22',
        '23th Day of Month'    =>   '23',
        '24th Day of Month'    =>   '24',
        '25th Day of Month'    =>   '25',
        '26th Day of Month'    =>   '26',
        '27th Day of Month'    =>   '27',
        '28th Day of Month'    =>   '28',
        '29th Day of Month'    =>   '29',
        '30th Day of Month'    =>   '30',
    ];

    /**
     * Minute based
     */
    protected $minute = [
        '5 Minute'   =>   '5',
        '10 Minute'  =>   '10',
        '15 Minute'  =>   '15',
        '20 Minute'  =>   '20',
        '25 Minute'  =>   '25',
        '30 Minute'  =>   '30',
        '35 Minute'  =>   '35',
        '40 Minute'  =>   '40',
        '45 Minute'  =>   '45',
        '50 Minute'  =>   '50',
        '55 Minute'  =>   '55'
    ];
}
