<?php

namespace App\Twig;

use App\Entity\AchatMonnaieAConfirmerAdherent;
use App\Entity\AchatMonnaieAConfirmerPrestataire;
use App\Entity\AchatMonnaieAdherent;
use App\Entity\AchatMonnaiePrestataire;
use App\Entity\Adherent;
use App\Entity\Comptoir;
use App\Entity\CotisationAdherent;
use App\Entity\CotisationPrestataire;
use App\Entity\Don;
use App\Entity\DonAdherent;
use App\Entity\DonPrestataire;
use App\Entity\Flux;
use App\Entity\GlobalParameter;
use App\Entity\Groupe;
use App\Entity\Groupeprestataire;
use App\Entity\HelloAsso;
use App\Entity\News;
use App\Entity\Prestataire;
use App\Entity\Rubrique;
use App\Entity\Siege;
use App\Entity\User;
use App\Entity\InformationPopup;
use App\Entity\InformationPopupUser;
use App\Enum\CurrencyEnum;
use App\Flux\AccountableInterface;
use App\Utils\CotisationUtils;
use App\Utils\OperationUtils;
use App\Utils\TAVCotisationUtils;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Security;
use Twig\Extension\AbstractExtension;
use Twig\TwigTest;

class AppExtension extends AbstractExtension
{
    public $em;
    public $security;
    public $container;
    public $paginator;
    public $session;
    public $operationUtils;
    public $cotisationUtils;
    public $tavCotisationUtils;

    public function __construct(
        ContainerInterface $container,
        Security $security,
        EntityManagerInterface $em,
        PaginatorInterface $paginator,
        SessionInterface $session,
        OperationUtils $operationUtils,
        CotisationUtils $cotisationUtils,
        TAVCotisationUtils $tavCotisationUtils
    ) {
        $this->em = $em;
        $this->security = $security;
        $this->container = $container;
        $this->paginator = $paginator;
        $this->session = $session;
        $this->operationUtils = $operationUtils;
        $this->cotisationUtils = $cotisationUtils;
        $this->tavCotisationUtils = $tavCotisationUtils;
    }

    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('getMLCPrestataire', [$this, 'getMLCPrestataire']),
            new \Twig_SimpleFunction('showModalGroupChoice', [$this, 'showModalGroupChoice']),
            new \Twig_SimpleFunction('getCurrentRole', [$this, 'getCurrentRole']),
            new \Twig_SimpleFunction('getCurrentComptoir', [$this, 'getCurrentComptoir']),
            new \Twig_SimpleFunction('getCurrentGroupe', [$this, 'getCurrentGroupe']),
            new \Twig_SimpleFunction('getCurrentPrestataire', [$this, 'getCurrentPrestataire']),
            new \Twig_SimpleFunction('isCotisationValid', [$this, 'isCotisationValid']),
            new \Twig_SimpleFunction('isCotisationValidForPresta', [$this, 'isCotisationValidForPresta']),
            new \Twig_SimpleFunction('isCotisationValidForAdherent', [$this, 'isCotisationValidForAdherent']),
            new \Twig_SimpleFunction('getSiege', [$this, 'getSiege']),
            new \Twig_SimpleFunction('isDevFixture', [$this, 'isDevFixture']),
            new \Twig_SimpleFunction('getLastNews', [$this, 'getLastNews']),
            new \Twig_SimpleFunction('getAllAdherents', [$this, 'getAllAdherents']),
            new \Twig_SimpleFunction('getAllPrestataires', [$this, 'getAllPrestataires']),
            new \Twig_SimpleFunction('getAllGroupePrestataires', [$this, 'getAllGroupePrestataires']),
            new \Twig_SimpleFunction('getAllComptoirs', [$this, 'getAllComptoirs']),
            new \Twig_SimpleFunction('getAllRubriques', [$this, 'getAllRubriques']),
            new \Twig_SimpleFunction('getAllGroupes', [$this, 'getAllGroupes']),
            new \Twig_SimpleFunction('getAllFlux', [$this, 'getAllFlux']),
            new \Twig_SimpleFunction('getValidCotisations', [$this, 'getValidCotisations']),
            new \Twig_SimpleFunction('isCurrentAccountable', [$this, 'isCurrentAccountable']),
            new \Twig_SimpleFunction('getDemandeAchatMonnaie', [$this, 'getDemandeAchatMonnaie']),
            new \Twig_SimpleFunction('hasDemandeAchatMonnaie', [$this, 'hasDemandeAchatMonnaie']),
            new \Twig_SimpleFunction('getLastOperations', [$this, 'getLastOperations']),
            new \Twig_SimpleFunction('mediaurl', [$this, 'mediaurl']),
            new \Twig_SimpleFunction('getWordpressApiKey', [$this, 'getWordpressApiKey']),
            new \Twig_SimpleFunction('getTotalMonnaieEmise', [$this, 'getTotalMonnaieEmise']),
            new \Twig_SimpleFunction('getStatsMonnaie', [$this, 'getStatsMonnaie']),
            new \Twig_SimpleFunction('getCurrencyName', [$this, 'getCurrencyName']),
            new \Twig_SimpleFunction('getPaymentReceiptUrlFromFlux', [$this, 'getPaymentReceiptUrlFromFlux']),
            new \Twig_SimpleFunction('getDonType', [$this, 'getDonType']),
            new \Twig_SimpleFunction('getLastTavCotisationForAdherent', [$this, 'getLastTavCotisationForAdherent']),
            new \Twig_SimpleFunction('getPrestaLastTransactionsExportDate', [$this, 'getPrestaLastTransactionsExportDate']),
            new \Twig_SimpleFunction('checkExistingRecurringPayment', [$this, 'checkExistingRecurringPayment']),
            new \Twig_SimpleFunction('parameter', function ($name) {
                return $this->container->getParameter($name);
            }),
            new \Twig_SimpleFunction('showInformationModal', [$this, 'showInformationModal']),
            new \Twig_SimpleFunction('getInformationPopupData', [$this, 'getInformationPopupData']),
        ];
    }

    public function getPaymentReceiptUrlFromFlux(Flux $flux): ?string
    {
        $helloasso = $this->em->getRepository(HelloAsso::class)->findOneBy(['flux' => $flux]);
        if (null != $helloasso) {
            return $helloasso->getPaymentReceiptUrl();
        }

        return '';
    }

    public function getDonType($don): ?string
    {
        if ($don instanceof DonAdherent || ($don instanceof Don && Don::TYPE_DON_ADHERENT == $don->getType())) {
            $cotis = $this->em->getRepository(CotisationAdherent::class)->findOneBy(['don' => $don]);
            if (null === $cotis) {
                $achat = $this->em->getRepository(AchatMonnaieAdherent::class)->findOneBy(['don' => $don]);

                return $achat;
            }

            return $cotis;
        } elseif ($don instanceof DonPrestataire || ($don instanceof Don && Don::TYPE_DON_PRESTATAIRE == $don->getType())) {
            $cotis = $this->em->getRepository(CotisationPrestataire::class)->findOneBy(['don' => $don]);
            if (null === $cotis) {
                $achat = $this->em->getRepository(AchatMonnaiePrestataire::class)->findOneBy(['don' => $don]);

                return $achat;
            }

            return $cotis;
        }

        return null;
    }

    public function getCurrencyName(string $currency): string
    {
        if (!in_array($currency, CurrencyEnum::getAvailableTypes())) {
            throw new \Exception('Opération impossible ! Type de currency  ' . $currency . ' inexistant !');
        }
        $symbol = $this->em->getRepository(GlobalParameter::class)->val(GlobalParameter::MLC_SYMBOL);
        switch ($currency) {
            case CurrencyEnum::CURRENCY_EMLC:
                return 'e' . $symbol;
            case CurrencyEnum::CURRENCY_MLC:
                return $symbol;
            case CurrencyEnum::CURRENCY_EURO:
                return '€';
            case CurrencyEnum::CURRENCY_MLC_NANTIE:
                return $symbol;
        }

        return '';
    }

    public function getMLCPrestataire(): ?Prestataire
    {
        try {
            $prestaMLC = $this->em->getRepository(Prestataire::class)->getPrestataireMLC();

            return $prestaMLC;
        } catch (\Exception $e) {
            return null;
        }

        return null;
    }

    public function getFilters()
    {
        return [
            new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecode']),
            new \Twig_SimpleFilter('safe_email', [$this, 'safeEmailFilter']),
        ];
    }

    public function jsonDecode($string)
    {
        return json_decode($string, true);
    }

    public function getWordpressApiKey()
    {
        $users = $this->em->getRepository(User::class)->findByRole('ROLE_API');
        if (count($users) <= 0) {
            // @TODO :erreur => crée un nouvel utilisateur API si non existant ?
            return '';
        }

        return $users[0]->getApiKey();
    }

    public function showModalGroupChoice()
    {
        if (null != $this->security->getUser()) {
            /* Enable display of modal after the first login of a user,
             * the first time there is an enable prestataire associated to the account.
             *
             * The user now realizes that a new role is available and can be picked up.
             *
             * Otherwise, in case prestataire is enabled after first login, user would login automaticaly as
             * adherent again without being noticed that prestataire has been enabled.
             */
            if($this->container->getParameter('presta_self_init_and_eval')) {
                $hasPrestataireEnabled = $this->security->getUser()->getPrestataires() && !$this->security->getUser()->getPrestataires()->isEmpty();
                $nowIsfirstLoginWithPrestaEnabled = $this->security->getUser()->getBeforeFirstLoginWithPrestaEnabled();
                if ($hasPrestataireEnabled && $nowIsfirstLoginWithPrestaEnabled) {
                    $this->security->getUser()->setBeforeFirstLoginWithPrestaEnabled(false);
                    $this->em->flush();
                    return true;
                }
            }
            if (count($this->security->getUser()->getPossiblegroups()) > 1 && 0 == count($this->security->getUser()->getGroups())) {
                return true;
            }
        }

        return false;
    }

    public function getCurrentRole()
    {
        $return = $this->security->getUser()->getGroups()[0]->getName();
        $user = $this->security->getUser();
        if (null != $this->session->get('_prestagere') && $user->isGranted('ROLE_PRESTATAIRE')) {
            $presta = $this->session->get('_prestagere');
            $presta = $this->em->getRepository(Prestataire::class)->findOneById($presta->getId());
            $return .= '<br/>' . $presta->__toString();
        } elseif (null != $user->getAdherent() && $user->isGranted('ROLE_ADHERENT')) {
            $return .= '<br/>' . $user->getAdherent()->__toString();
        } elseif (null != $this->session->get('_comptoirgere') && $user->isGranted('ROLE_COMPTOIR')) {
            $comptoir = $this->session->get('_comptoirgere');
            $comptoir = $this->em->getRepository(Comptoir::class)->findOneById($comptoir->getId());
            $return .= '<br/>' . $comptoir->__toString();
        } elseif (null != $this->session->get('_groupegere') && $user->isGranted('ROLE_GESTION_GROUPE')) {
            $groupe = $this->session->get('_groupegere');
            $groupe = $this->em->getRepository(Groupe::class)->findOneById($groupe->getId());
            $return .= '<br/>' . $groupe->__toString();
        }

        return $return;
    }

    public function getCurrentComptoir()
    {
        if (!$this->session->has('_comptoirgere')) {
            return null;
        }

        return $this->em->getRepository(Comptoir::class)->findOneById($this->session->get('_comptoirgere')->getId());
    }

    public function getCurrentGroupe()
    {
        if (!$this->session->has('_groupegere')) {
            return null;
        }

        return $this->em->getRepository(Groupe::class)->findOneById($this->session->get('_groupegere')->getId());
    }

    public function getCurrentPrestataire()
    {
        if (!$this->session->has('_prestagere')) {
            return null;
        }

        return $this->em->getRepository(Prestataire::class)->findOneById($this->session->get('_prestagere')->getId());
    }

    /**
     * isCotisationValid.
     *
     * @param UserInterface|null $user
     *
     * @return bool|date
     */
    public function isCotisationValid(?UserInterface $user = null)
    {
        return $this->cotisationUtils->isCotisationValid($user);
    }

    /**
     * [isCotisationValidForPresta.
     *
     * @param Prestataire|null $presta
     *
     * @return bool|date
     */
    public function isCotisationValidForPresta(?Prestataire $presta = null)
    {
        return $this->cotisationUtils->isCotisationValidForPresta($presta);
    }

    /**
     * [isCotisationValidForAdherent.
     *
     * @param Adherent|null $adherent
     *
     * @return bool|date
     */
    public function isCotisationValidForAdherent(?Adherent $adherent = null)
    {
        return $this->cotisationUtils->isCotisationValidForAdherent($adherent);
    }

    /**
     * [getLastTavCotisationForAdherent.
     *
     * @param Adherent|null $adherent
     *
     * @return bool|date
     */
    public function getLastTavCotisationForAdherent(?Adherent $adherent = null)
    {
        return $this->tavCotisationUtils->getLastTavCotisationForAdherent($adherent);
    }

    public function isDevFixture(?string $username = null)
    {
        if (null == $username) {
            $user = $this->em->getRepository(User::class)->findBy(['username' => ['user_redacteur', 'user_tresorier', 'user_prestataire', 'user_contact']]);

            return 4 === count($user);
        }

        return null != $this->em->getRepository(User::class)->findOneByUsername($username);
    }

    public function getSiege()
    {
        return $this->em->getRepository(Siege::class)->getTheOne();
    }

    public function getLastNews($limit = 5)
    {
        return $this->em->getRepository(News::class)->findLatest($limit)->getResult();
    }

    public function getAllAdherents()
    {
        return $this->em->getRepository(Adherent::class)->findOrderByName();
    }

    public function getAllPrestataires($orderBy = 'raison', $direction = 'ASC', $limit = null)
    {
        return $this->em->getRepository(Prestataire::class)->findDefault('all', $orderBy, $direction, $limit);
    }

    public function getAllGroupePrestataires($type = '')
    {
        if (!empty($type)) {
            return $this->em->getRepository(Groupeprestataire::class)->findBy(['type' => $type, 'enabled' => true]);
        }

        return $this->em->getRepository(Groupeprestataire::class)->findBy(['enabled' => true]);
    }

    public function getAllComptoirs()
    {
        return $this->em->getRepository(Comptoir::class)->findBy(['enabled' => true], ['name' => 'ASC']);
    }

    public function getAllRubriques()
    {
        return $this->em->getRepository(Rubrique::class)->findBy(['enabled' => true], ['name' => 'ASC']);
    }

    public function isCurrentAccountable(User $user, AccountableInterface $object)
    {
        $obj = $this->operationUtils->getCurrentAccountable($user);
        if ($obj->getId() == $object->getId() && get_class($obj) == get_class($object)) {
            return true;
        }

        return false;
    }

    public function getDemandeAchatMonnaie($reconverti = true)
    {
        $obj = $this->operationUtils->getCurrentAccountable($this->security->getUser());
        if ($obj instanceof Prestataire) {
            $q = $this->em->getRepository(AchatMonnaieAConfirmerPrestataire::class)->findBy(['destinataire' => $obj, 'reconverti' => $reconverti], ['createdAt' => 'DESC']);
        } elseif ($obj instanceof Adherent) {
            $q = $this->em->getRepository(AchatMonnaieAConfirmerAdherent::class)->findBy(['destinataire' => $obj, 'reconverti' => $reconverti], ['createdAt' => 'DESC']);
        }

        return $q;
    }

    public function hasDemandeAchatMonnaie($reconverti = true): bool
    {
        $q = $this->getDemandeAchatMonnaie($reconverti);

        return null != $q && count($q) > 0;
    }

    public function getTotalMonnaieEmise(): float
    {
        $return = $this->em->getRepository(Flux::class)->getTotalVenteAchat();
        if (null == $return) {
            return 0;
        }

        return $return;
    }

    public function getStatsMonnaie(string $currency = CurrencyEnum::CURRENCY_EMLC): float
    {
        $siege = $this->em->getRepository(Siege::class)->getTheOne();

        if (!empty($currency)) {
            if (!in_array($currency, [CurrencyEnum::CURRENCY_EMLC, CurrencyEnum::CURRENCY_MLC_NANTIE])) {
                return (float) 0;
            }

            if ($currency == CurrencyEnum::CURRENCY_MLC_NANTIE) {
                return round($siege->getAccountWithCurrency($currency)->getBalance(), 0);
            }
            return $siege->getAccountWithCurrency($currency)->getBalance();
        }

        return $siege->getEmlcAccount()->getBalance();
    }

    public function getLastOperations(Request $request, User $user, string $currency = null, int $limit = 10)
    {
        $query = $this->operationUtils->getUserOperationsByCurrency($request, $currency);

        // return $query->getResult();
        if (null != $query) {
            $pagination = $this->paginator->paginate(
                $query, /* query NOT result */
                $request->query->getInt('page', 1)/*page number*/ ,
                $limit/*limit per page*/
            );

            return $pagination;
        }

        return null;
    }

    public function getValidCotisations(User $user, Request $request)
    {
        $cotisationsValid = [];
        $cotisations = $this->getAllFlux($user, $request, 'cotisation');
        foreach ($cotisations as $keys => $cotisation) {
            if (null != $cotisation->getCotisationInfos() && $cotisation->getCotisationInfos()->isRecu()) {
                $cotisationsValid[] = $cotisation;
            }
        }
        return $cotisationsValid;
    }

    public function getAllFlux(User $user, Request $request, $parenttype = null)
    {
        $query = null;
        if (null != $this->session->get('_prestagere')) {
            if ($this->security->isGranted('ROLE_PRESTATAIRE')) {
                $query = $this->em->getRepository(Flux::class)->getQueryByPrestataire($this->session->get('_prestagere'), $parenttype);
            } elseif ($this->security->isGranted('ROLE_CAISSIER')) {
                $query = $this->em->getRepository(Flux::class)->getQueryByCaissier($this->session->get('_prestagere'));
            }
        } elseif (null != $user->getAdherent()) {
            $query = $this->em->getRepository(Flux::class)->getQueryByAdherent($user->getAdherent(), $parenttype);
        } elseif (null != $this->session->get('_comptoirgere')) {
            $query = $this->em->getRepository(Flux::class)->getQueryByComptoir($this->session->get('_comptoirgere'));
        } elseif (null != $this->session->get('_groupegere')) {
            $query = $this->em->getRepository(Flux::class)->getQueryByGroupe($this->session->get('_groupegere'));
        }
        if (null != $query) {
            $pagination = $this->paginator->paginate(
                $query, /* query NOT result */
                $request->query->getInt('page', 1)/*page number*/ ,
                10/*limit per page*/
            );

            return $pagination;
        }

        return null;
    }

    public function getAllGroupes()
    {
        return $this->em->getRepository(Groupe::class)->findBy(['enabled' => true], ['name' => 'ASC']);
    }

    public function mediaurl($media, $format)
    {
        $provider = $this->container->get($media->getProviderName());

        return $provider->generatePublicUrl($media, $format);
    }

    public function getTests(): array
    {
        return [
            new TwigTest('instanceof', [$this, 'instanceof']),
        ];
    }

    public function instanceof($var, $instance)
    {
        $reflexionClass = new \ReflectionClass($instance);

        return $reflexionClass->isInstance($var);
    }

    /**
     * Protects email address.
     * (inspired by : https://github.com/getgrav/grav/blob/develop/system/src/Grav/Common/Twig/TwigExtension.php ).
     *
     * @param string $str
     *
     * @return string
     */
    public function safeEmailFilter($str)
    {
        $email = '';
        for ($i = 0, $len = strlen($str); $i < $len; ++$i) {
            $j = mt_rand(0, 1);
            if (0 === $j) {
                $email .= '&#' . ord($str[$i]) . ';';
            } elseif (1 === $j) {
                $email .= $str[$i];
            }
        }

        return str_replace('@', '&#64;', $email);
    }

    public function getPrestaLastTransactionsExportDate(?Adherent $adherent = null)
    {
        if (!$this->session->has('_prestagere')) {
            return null;
        }

        $presta = $this->em->getRepository(Prestataire::class)->findOneById($this->session->get('_prestagere')->getId());

        $datetime_last_export = $presta->getLastTransactionsExportDatetime();

        $res = "";
        if (null == $datetime_last_export) {
            $res = $presta->getCreatedAt()->format('d/m/Y H\hi');
        } else {
            $res = $datetime_last_export->format('d/m/Y H\hi');
        }

        return $res;
    }

    /**
     * [checkExistingRecurringPayment].
     *
     * @param String $userEmail
     *
     * @return String
     */
    public function checkExistingRecurringPayment($userEmail)
    {
        return $this->tavCotisationUtils->checkExistingRecurringPayment($userEmail);
    }

    /**
     * Display an informative modale destined to users at app loading, if conditions are required.
     * 
     * Conditions :
     * - user is adherent (forced for now)
     * - user hasn't clicked on the modal validation button yet (InformationPopupUser instance not created or exists with hasValidated property at false)
     */
    public function showInformationModal()
    {
        $user = $this->security->getUser();
        if (null != $user && $this->security->isGranted('ROLE_ADHERENT')) {
            // There should be only one enabled popup at a given time
            $activePopup = $this->em->getRepository(InformationPopup::class)->findOneBy(['enabled' => true]);

            if (!is_null($activePopup)) {
                $activePopupUser = $this->em->getRepository(InformationPopupUser::class)->findOneBy(['informationPopup' => $activePopup, 'user' => $user]);

                if (null == $activePopupUser || false == $activePopupUser->getHasValidated()) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * If the information modale is shown, get its data
     */
    public function getInformationPopupData()
    {
        return $this->em->getRepository(InformationPopup::class)->findOneBy(['enabled' => true]);
    }
}