<?php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\LockableTrait;
use App\Service\MailService;
use Symfony\Component\Console\Input\ArrayInput;

class CronCommand extends Command
{
    private $container;
    private $em;
    private $mail;
    private $output;
    private $filesystem;
    private $rootlog;
    private $appname;
    private $noreply;

    use LockableTrait;
    
    public function __construct(ContainerInterface $container, EntityManagerInterface $em, mailService $mail)
    {
        parent::__construct();
        $this->container = $container;
        $this->em = $em;
        $this->mail = $mail;
    } 

    protected function configure()
    {
        $this
            ->setName('app:Cron')
            ->setDescription('Execution of the cron command')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->output       = $output;
        $this->filesystem   = new Filesystem();
        $this->rootlog      = $this->container->get('kernel')->getProjectDir()."/../var/log/";
        $this->noreply      = $this->container->getParameter('noreply');
        $this->appname = $this->em->getRepository("App\Entity\Config")->findOneBy(["id"=>"appname"])->getValue();

        $cron_activate = $this->container->getParameter('cron_activate');
        if(!$cron_activate)
        {
            $this->writelnred('CRON désactivé');
            return Command::SUCCESS;
        }

        if (!$this->lock()) {
            $this->output->writeln("CRON LOCK");
            return Command::SUCCESS;
        }


        // On s'assure qu'il n'y a pas de tache cron en trop
        $purgecrons=$this->em->getRepository('App\Entity\Cron')->findAll();
        foreach($purgecrons as $purge) {
            if($purge->getId() > 2000) {
                $this->em->remove($purge);
            }
        }
        $this->em->flush();
        
        // On récupére la liste des jobs à exécuter
        $crons = $this->em->getRepository('App\Entity\Cron')->toexec();
        $i=0;
        
        if($crons) {
            $now=new \DateTime();
            $this->writelnred('');
            $this->writelnred('');
            $this->writelnred('');
            $this->writelnred('');
            $this->writelnred('==========================================================================================================');
            $this->writelnred('== CRON ==================================================================================================');
            $this->writelnred('==========================================================================================================');   
            $this->writeln   ('Date = '.$now->format('Y-m-d H:i:s'));   
        }

        // Cas particulier de la synchro
        // Dans la synchro il y a un clear du manager ce qui perturbe totalement le manager de Core:Exec
        // Il pert le lien avec la boucle sur crons
        // Alors si dans le cron il y a la synchro alors on n'execute que lui le reste sera executé lors du prochain passage
        $cronsynchro=$this->em->getRepository('App\Entity\Cron')->find(100);
        if($cronsynchro&&in_array($cronsynchro,$crons)) {
            $crons=[$cronsynchro];

        }
        
        foreach($crons as $cron) {
            $i++;
            
            // Id du cron
            $idcron = $cron->getId();

            // Flag d'execution en cours
            $now=new \DateTime();
            $cron->setStartexecdate($now);
            //$cron->setStatut(1);
            $this->em->persist($cron);
            $this->em->flush();

            // Récupération de la commande
            $command = $this->getApplication()->find($cron->getCommand());
            
            // Réccuépration des parametres
            $jsonparameter=json_decode($cron->getJsonargument(),true);

            // Parametre id du cron actuel
            $jsonparameter["cronid"]=$cron->getId();

            // Parametre si dernière execution
            $lastchance=false;
            if($cron->getRepeatcall()>0)
                $lastchance=($cron->getRepeatexec()+1==$cron->getRepeatcall());
            $jsonparameter["lastchance"]=$lastchance;

            // Formater la chaine de parametre
            $parameter = new ArrayInput($jsonparameter);

            // Executer la commande
            $returnCode=Command::FAILURE;
            try{
                $returnCode = $command->run($parameter, $output);

                // Revenir sur le cron encours à cause du clear du manager présent dans la synchro
                // Sinon le manager se pomme et génère des nouveaux enregistrement plutot que mettre à jour celui en cours
                $cron=$this->em->getRepository('App\Entity\Cron')->find($idcron);
            }
            catch(\Exception $e) { 
                $this->writelnred("JOB EN ERREUR"); 
            }            

            // Flag de fin d'execution
            $now=new \DateTime();
            $cron->setEndexecdate($now);

            // Date prochaine execution
            if($cron->getrepeatinterval()>=0) {
                // Si interval par heure
                if(fmod($cron->getRepeatinterval(),3600)==0) 
                    $next=clone $cron->getNextexecdate();
                else
                    $next=new \DateTime();

                $next->add(new \DateInterval('PT'.$cron->getRepeatinterval().'S'));
                $cron->setNextexecdate($next);
            }
            
            // Statut OK/KO/Retry
            $cron->setStatut(2);
            if($returnCode==Command::FAILURE) {
                $cron->setStatut(3);
                if($cron->getRepeatcall()>0) $cron->setRepeatexec($cron->getRepeatexec()+1);

                // Envoyer un mail à l'ensemble des administrateurs
                $this->sendMailerror($cron);
            }

            $this->em->persist($cron);
            $this->em->flush();
        }

        if($crons) {
            $this->writelnred("==");
            $this->writelnred("FIN CRON");
            $this->writelnred("==");
            $this->writelnred("");
        }

        return Command::SUCCESS;
    }

    private function sendMailerror($cron) {
        // Email à l'ensemble administrateurs pour les prévenir qu'il y a une personne à valider
        $emailadmins= $this->em->createQueryBuilder()
                            ->select('table.email')
                            ->from("App\Entity\User",'table')
                            ->where('table.role = :value')
                            ->setParameter("value", "ROLE_ADMIN")
                            ->getQuery()
                            ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR);
        $to=array();
        $from =  $this->noreply;
        $fromName = $this->appname;                
        foreach($emailadmins as $emailadmin) {
            array_push($to,$emailadmin["email"]);
        }

        $subject=$this->appname." : ERREUR SUR EXECUTION JOB";
        $body="ATTENTION UN JOB EST EN ERREUR. MERCI DE VERIFIER LES LOGS.<br>Command = ".$cron->getCommand()."<br>Description = ".$cron->getDescription();
        $this->mail->sendEmail($subject, $body, $to, $from, $fromName);  
    }

    private function writelnred($string) { 
        $this->output->writeln('<fg=red>'.$string.'</>');
        $this->filesystem->appendToFile($this->rootlog.'cron.log', $string."\n");
    }
    private function writeln($string) { 
        $this->output->writeln($string);
        $this->filesystem->appendToFile($this->rootlog.'cron.log', $string."\n");
    } 
}
