<?php

namespace AppBundle\Controller\Cron;

use AppBundle\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Mail\ItdlcMailer;
use AppBundle\Entity\MerITDLC;
use AppBundle\Entity\MerHistoryITDLC;
use AppBundle\Entity\OperationITDLC;
use AppBundle\Enum\OperationITDLCState;
use Geocoder\Collection;
use Geocoder\Model\Coordinates;
use Geocoder\Query\GeocodeQuery;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class CronjobController extends Controller
{

    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * @var ItdlcMailer
     */
    private $itdlcMailer;

    public function __construct(
        EntityManagerInterface $em,
        ItdlcMailer $itdlcMailer
    )
    {
        $this->em = $em;

        $this->itdlcMailer = $itdlcMailer;
    }

    /**
     * Envoi des rappels pour les mises en relation ITDLC
     *
     * @Route("/closeOperations", name="cron_close_operations")
     *
     * @Method("GET")
     *
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\Response
     * @throws \Exception
     */
    public function CloseOperationsAction(Request $request)
    {
       
        $operations =  $this->em->getRepository(OperationITDLC::class)->findAllOperationsToBeClosed();

        if ($operations) {
            
            foreach ($operations as $ope) {
                $log[] = "--------------------------------------------------------";
                $ope->setState(OperationITDLCState::CLOSED);
                $log[] = "[" . date('d/m/Y H:i:s') . "] operation ".$ope->getId()." closed";
                $reponseEng = $this->itdlcMailer->sendFormSatisfaction('engineer', $ope);
                $reponseTeacher = $this->itdlcMailer->sendFormSatisfaction('teacher', $ope);

                if (!empty($reponseEng[0]['code']) && $reponseEng[0]['code'] == "success") {
                    $log[] = "[" . date('d/m/Y H:i:s') . "] Engineer " . $ope->getInge()->getEmail() . ' email sent successfuly';
                } else {
                    $log[] = "[" . date('d/m/Y H:i:s') . "] Engineer " . $ope->getInge()->getEmail() . ' email unsuccessfully sent';
                }

                if (!empty($reponseTeacher[0]['code']) && $reponseTeacher[0]['code'] == "success") {
                    $log[] = "[" . date('d/m/Y H:i:s') . "] Teacher " . $ope->getProf()->getEmail() . ' email sent successfuly';
                } else {
                    $log[] = "[" . date('d/m/Y H:i:s') . "] Teacher " . $ope->getInge()->getEmail() . ' email unsuccessfully sent';
                }
                $log[] = "--------------------------------------------------------";
                $this->em->persist($ope);
            }

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

        $log[] = "[" . date('d/m/Y H:i:s') . "] " . count($operations) . ' operation(s) closed ';
       

        return $this->render('Cronjob/log.html.twig', array(
            'logs' => $log
         ));

    }


    /**
     * Envoi des rappels pour les mises en relation ITDLC
     *
     * @Route("/remindersMerITDLC/{type}", name="cron_reminder_mer_itdlc")
     *
     * @Method("GET")
     *
     * @param string $type
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\Response
     * @throws \Exception
     */
    public function RemindersMerITDLCAction($type, Request $request)
    {
        $debug = $request->query->get('debug') ? true : false;

        $logs = [];
      

        if ($type == "5j") {
            $mers = $this->em->getRepository(MerITDLC::class)->getMerWithoutAnswer();

            if (!empty($mers)) {
                foreach ($mers as $mer) {
                    if (!$debug) {
                        $reponse =  $this->itdlcMailer->sendReminderMerToInge($mer);
                    }
                    if (!empty($reponse[0]) && $reponse[0]['code'] == "success") {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Email sent to ' . $mer->getInge()->getEmail() . " (" . $mer->getInge()->getFullName() . ") => IDTLC: " . $mer->getOpe()->getId();
                        if (!$debug) {
                            $mer->setReminderAt();

                            $merHistory = new MerHistoryITDLC();
                            $merHistory->setInfos("Envoi d'un rappel à " . $mer->getInge()->getFullName());
                            $mer->addHistory($merHistory);

                            $this->em->persist($mer);
                        }
                    } else {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Error sending email  to op #' . $mer->getOpe()->getId();
                    }
                }

                if (!$debug) {
                    $this->em->flush();
                }
            } else {
                $logs[] = "[" . date('d/m/Y H:i:s') . "] " . '0 reminder to send';
            }

        } elseif ($type == "7j") {

            $opes = $this->em->getRepository(OperationITDLC::class)->findComingSoonOperations();

            if (!empty($opes)) {
                foreach ($opes as $ope) {
                    if (!$debug ){
                        $reponses = $this->itdlcMailer->sendReminderComingSoonOperation($ope);
                    }

                    if (!empty($reponses[0][0]) && $reponses[0][0]['code'] == "success") {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Email sent to ' . $ope->getProf()->getEmail() . " (" . $ope->getProf()->getFullName() . ") => IDTLC: " . $ope->getId();

                    } else {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Error email didn\t send to ' . $ope->getProf()->getEmail() . " (" . $ope->getProf()->getFullName() . ") => IDTLC: " . $ope->getId();
                    }

                    if (!empty($reponses[1][0]) && $reponses[1][0]['code'] == "success") {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Email sent to ' . $ope->getInge()->getEmail() . " (" . $ope->getInge()->getFullName() . ") => IDTLC: " . $ope->getId();

                    } else {
                        $logs[] =  "[" . date('d/m/Y H:i:s') . "] " . 'Error email didn\t send to ' . $ope->getInge()->getEmail() . " (" . $ope->getInge()->getFullName() . ") => IDTLC: " . $ope->getId();
                    }
                }

                if (!$debug) {$this->em->flush();}
            } else {
                $logs[] = "[" . date('d/m/Y H:i:s') . "] " . '0 reminder to send';
            }
        } else if ($type == "10j") {

            $mers = $this->em->getRepository(MerITDLC::class)->getMerWithoutAnswerAfterReminders();

            if (!empty($mers)) {
                foreach ($mers as $mer) {
                    if (!$debug) {
                        $reponse = $this->itdlcMailer->sendReminderMerToTeacher($mer);
                    }

                    if (!empty($reponse[0]) && $reponse[0]['code'] == "success") {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Email sent to ' . $mer->getOpe()->getProf()->getEmail() . " (" . $mer->getOpe()->getProf()->getFullName() . ") => IDTLC: " . $mer->getOpe()->getId();
                        
                        $ope = $mer->getOpe();
                        $inge = $ope->getInge();
                        $numNoAnswer = intval($inge->getNumberNoAnswer()) + 1;
                        // Rendre actif l'intervenant

                        $inge->setNumberNoAnswer($numNoAnswer);
                        $this->em->persist($inge);

                        $ope->setInge(null);
                        $ope->setState(OperationITDLCState::NEW);
                        $this->em->persist($ope);

                        $merHistory = new MerHistoryITDLC();
                        $merHistory->setInfos("Aucune réponse à votre proposition depuis 10 jours de " . $mer->getInge()->getFullName());
                        $mer->addHistory($merHistory);

                        $this->em->persist($mer);
                    } else {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Error email didn\t send to ' . $mer->getOpe()->getProf()->getEmail() . " (" . $mer->getOpe()->getProf()->getFullName() . ") => IDTLC: " . $mer->getOpe()->getId();
                    }
                }

                if (!$debug) {
                    $this->em->flush();
                }
            } else {
                $logs[] = "[" . date('d/m/Y H:i:s') . "] " . '0 reminder to send';
            }


        } elseif ($type == "30j") {

            $opes = $this->em->getRepository(OperationITDLC::class)->findDemandeWithoutMer();

            if (!empty($opes)) {
                foreach ($opes as $ope) {
                    if (!$debug) {
                        $reponse = $this->itdlcMailer->sendOperationNoMerToTeacher($ope);
                    }
                    if (!empty($reponse[0]) && $reponse[0]['code'] == "success") {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Email sent to ' . $ope->getProf()->getEmail() . " (" . $ope->getProf()->getFullName() . ") => IDTLC: " . $ope->getId();
                    } else {
                        $logs[] = "[" . date('d/m/Y H:i:s') . "] " . 'Error email didn\t send to ' . $ope->getProf()->getEmail() . " (" . $ope->getProf()->getFullName() . ") => IDTLC: " . $ope->getId();
                    }
                }
            } else {
                $logs[] = "[" . date('d/m/Y H:i:s') . "] " . '0 reminder to send';
            }
        }
        
        return $this->render('Cronjob/log.html.twig', array(
           'logs' => $logs
        ));
    }



     /**
     * geocodage des utilisateurs non géocodés
     *
     * @Route("/geocodeUsers/{provider}/{max}", name="geocode_users")
     *
     * @Method("GET")
     * 
     * @param String $provider
     * @param String $max
     * @return \Symfony\Component\HttpFoundation\Response
     * @throws \Exception
     */
    public function geocodeUsersAction($provider, $max, Request $request)
    {
        $logs = [];
        
        $force = null !== $request->query->get('force') ? true : false;

        $validator = $this->get('validator');

        $geocoder = $this->get('bazinga_geocoder.provider.' . $provider);
        $qtyUpdated = 0;

        $users = $this->findUsers($this->em, $validator, $max, $force);


        // Prevent Google Maps request usage limits for free accounts (50 every 1 second)
        // @see https://developers.google.com/maps/documentation/geocoding/usage-limits
        // This is purely theorical, because it assumes that the code takes 0 seconds to execute.
        $chunks = array_chunk($users, 50);

       
        foreach ($chunks as $chunk) {
            foreach ($chunk as $user) {
                $geocodeQuery = $this->createGeocodeQuery($user);
        
                $adresses = $geocoder->geocodeQuery($geocodeQuery);

                $isUserUpdated = $this->updateUserCoordinates($user, $adresses);

                if ($isUserUpdated) {
                    $qtyUpdated = $qtyUpdated + 1;
                    $this->em->persist($user);
                }
            }
        }
        if ($qtyUpdated > 0) {
            $log[] = 'Saving to database...';
            $this->em->flush();
        }

        $log[] = count($users) . ' user(s) geocoded. ' . $qtyUpdated . ' user(s) updated.';

        return $this->render('Cronjob/log.html.twig', array(
            'logs' => $logs
         ));
    }
    
    /**
     * Find geocodable users from the DB, where the personalAddress and personalZipcode properties
     * has been validated using the Symfony validator.
     * The property validation is needed because the validation was absent
     * from the beginning. It means that some user accounts could have bad formatted data.
     * 
     * @param EntityManagerInterface $em
     * @param ValidatorInterface $validator
     * @param int $max
     * @param bool $force
     * @return array List of users
     */
    private function findUsers(
        EntityManagerInterface $em,
        ValidatorInterface $validator,
        int $max = 0,
        bool $force = false
    ): array {
        $qb = $this->em->createQueryBuilder();

        // Find users from the database where the personal address or zipcode is set
        $qb->select('u')
            ->from(User::class, 'u')
            ->where(
                $qb->expr()->orX(
                    $qb->expr()->isNotNull('u.personalAddress'),
                    $qb->expr()->isNotNull('u.personalZipcode')
                )
            );

        if (!$force) {
            $qb->andWhere($qb->expr()->isNull('u.personalLatitude'))
                ->andWhere($qb->expr()->isNull('u.personalLongitude'));
        }

        if ($max > 0) {
            $qb->setMaxResults($max);
        }

        $users = $qb->getQuery()->getResult();
        
        // Keep the users were the Symfony validation is ok for the 2 fields
        return array_filter($users, function (User $user) use ($validator) {
            $addressViolations = $validator->validateProperty($user, 'personalAddress', 'profile');
            $zipcodeViolations = $validator->validateProperty($user, 'personalZipcode', 'profile');

            return 0 === count($addressViolations) && 0 === count($zipcodeViolations);
        });
    }

    /**
     * Format the query for the geocoding service
     *
     * @param User $user
     * @return GeocodeQuery 
     */
    private function createGeocodeQuery(User $user): GeocodeQuery
    {
        $address = $user->getPersonalAddress();

        $location = $address . ($address ? ', ' : '') . $user->getPersonalZipcode();

        return GeocodeQuery::create($location)->withLocale('fr');
    }

    /**
     * Update the user personal coordinates, from a list of geocoded adresses
     *
     * @param User $user
     * @param Collection $adresses List of geocoded adresses
     * @return bool Indicates if the user has been updated
     */
    private function updateUserCoordinates(User &$user, Collection $adresses): bool
    {
        $isUpdated = false;

        if (!$adresses->isEmpty()) {
            // Assume the first address is the one we need
            $coordinates = $adresses->first()->getCoordinates();
            
            // Update the user with the latitude and longitude found
            if ($coordinates instanceof Coordinates) {
                $user->setPersonalLatitude($coordinates->getLatitude());
                $user->setPersonalLongitude($coordinates->getLongitude());

                $isUpdated = true;
            }
        }

        return $isUpdated;
    }

}
