<?php

namespace App\Utils;

use App\Entity\Adherent;
use App\Entity\Siege;
use App\Entity\Flux;
use App\Entity\CotisationTavReversement;
use App\Entity\CotisationTavPrelevement;
use App\Enum\MoyenEnum;
use App\Utils\CustomEntityManager;
use Symfony\Component\Security\Core\Security;

class TAVCotisationUtils
{
    private $em;
    private $security;
    private $operationUtils;

    public function __construct (
        CustomEntityManager $em,
        Security $security,
        OperationUtils $operationUtils
    ) {
        $this->em = $em;
        $this->security = $security;
        $this->operationUtils = $operationUtils;
    }

    /**
     * Check if cotisation already exist this month the the recipient of the given Flux.
     */
    public function checkExistingCotisation(Flux $flux) 
    {
        $first_day_this_month = date('Y-m-01');
        $last_day_this_month  = date('Y-m-t');

        $existing = $this->em->getRepository(Flux::class)->getTavCotisationsBetweenDates(
            $flux->getDestinataire(),
            $first_day_this_month,
            $last_day_this_month
        );

        return count($existing) > 0;
    }

    /**
     * First method to calculate allowance: 
     * according to a contribution rate defined in user's profile (ProfilDeCotisation).
     * 
     * Apply the cotisation profile rate to the amount paid 
     * and register the complement as a new flux (only if rate != 1)
     * 
     * Warning: EntityManager not flushed here.
     */
    public function applyTauxCotisation(Flux $flux)
    {
        $profile = $flux->getDestinataire()->getProfilDeCotisation();
        $cotisationTaux = $profile->getTauxCotisation();
        
        // don't need to create an other Flux if the rate is 1
        if ($cotisationTaux != 1) {
            // calculate the mlc amount the user will receive
            $cotisationAmount = $profile->getMontant();
            $mlcAmount = round($cotisationAmount * $cotisationTaux);
    
            // get the difference between what the user paid and what he•she's supposed to receive
            $amountDiff = $mlcAmount - $cotisationAmount;
    
            if ($flux->getExpediteur() instanceof Siege) {
                $siege = $flux->getExpediteur();
            } else {
                $siege = $flux->getExpediteur()->getGroupe()->getSiege();
            }
    
            if ($amountDiff > 0) {
                // User should receive more than he•she paid: send a new flux to the user to complete its cotisation
                $fluxCotis = new CotisationTavReversement();
                $fluxCotis->setExpediteur($siege);
                $fluxCotis->setDestinataire($flux->getDestinataire());
                $fluxCotis->setMontant($amountDiff);
                $fluxCotis->setReference("Reversement cotisation après paiement de " . $cotisationAmount . "€ et application du taux " . $cotisationTaux);
            } else {
                // User should receive less than he•she paid: fetch the difference from his account 
                $fluxCotis = new CotisationTavPrelevement();
                $fluxCotis->setExpediteur($flux->getDestinataire());
                $fluxCotis->setDestinataire($siege);
                $fluxCotis->setMontant(-$amountDiff);
                $fluxCotis->setReference("Prélèvement cotisation après paiement de " . $cotisationAmount . "€ et application du taux " . $cotisationTaux);
            }
    
            $fluxCotis->setOperateur($flux->getOperateur());
            $fluxCotis->setRole($flux->getRole());
            $fluxCotis->setMoyen(MoyenEnum::MOYEN_EMLC);
            $this->em->persist($fluxCotis);
            $this->operationUtils->executeOperations($fluxCotis);
        }
    }

    /**
     * Second method to calculate allowance: 
     * allowance based on user's household.
     * 
     * Rules are as follow:
     * - 150 emlc for the first person in user's household
     * - 75 emlc for each other adult
     * - 75 emlc amount for each dependant child, with a percentage applied if the child is in shared custody:
     * 25%, 50% or 75% depending on the shared custody arrangement
     * 
     * Once the full amount is calculated, cap user's balance.
     * User account balance is capped at twice the amount previously calculated.
     * 
     * @param Adherent $adherent (by ref)
     */
    public function calculateAllowanceAccordingToHousehold(&$adherent) {
        // TODO base amounts to param in .env, or in global params ?

        // base allowance, for one adult
        $mlcAllowanceAmount = 150;

        $adultsCount = $adherent->getHouseholdAdultCount();
        if ($adultsCount == null) {
            return;
        }

        // increment for each other adult in the household
        $mlcAllowanceAmount += 75 * ($adultsCount - 1);

        // increment allowance for each dependant child, depending on the shared custody arrangement
        $dependentChildren = $adherent->getDependentChildren();
        foreach ($dependentChildren as $child) {
             $childAllowanceAmount = 75;

             $sharedCustodyPercentage = $child->getSharedCustodyPercentage();
             if ($sharedCustodyPercentage != null) {
                $childAllowanceAmount = $childAllowanceAmount * $sharedCustodyPercentage;
             }

            $mlcAllowanceAmount += $childAllowanceAmount;
        }

        $adherent->setAllocationAmount($mlcAllowanceAmount);
    }

    /**
     * Method called to create Flux based on allowance amount (for household based allowance).
     */
    public function applyHouseholdAllowance(Flux $flux) {
        // get allowance
        $adherent = $flux->getDestinataire();
        $cotisationAmount = $flux->getMontant();
        
        // get the mlc amount the user is supposed to receive
        $mlcAllowanceAmount = $adherent->getAllocationAmount();

        if ($flux->getExpediteur() instanceof Siege) {
            $siege = $flux->getExpediteur();
        } else {
            $siege = $flux->getExpediteur()->getGroupe()->getSiege();
        }

        // get the difference between what the user paid and what he•she's supposed to receive
        $amountDiff = $mlcAllowanceAmount - $cotisationAmount;

        if ($amountDiff > 0) {
            // User should receive more than he•she paid: send a new flux to the user to complete its cotisation
            $fluxCotis = new CotisationTavReversement();
            $fluxCotis->setExpediteur($siege);
            $fluxCotis->setDestinataire($adherent);
            $fluxCotis->setMontant($amountDiff);
            $fluxCotis->setReference("Reversement du complément de cotisation après paiement de " . $cotisationAmount . "€ pour une allocation de " . $mlcAllowanceAmount . " MonA.");
        } else {
            // User should receive less than he•she paid: fetch the difference from his account 
            $fluxCotis = new CotisationTavPrelevement();
            $fluxCotis->setExpediteur($adherent);
            $fluxCotis->setDestinataire($siege);
            $fluxCotis->setMontant(-$amountDiff);
            $fluxCotis->setReference("Prélèvement du complément de cotisation après paiement de " . $cotisationAmount . "€ pour une allocation de " . $mlcAllowanceAmount . " MonA.");
        }

        $fluxCotis->setOperateur($flux->getOperateur());
        $fluxCotis->setRole($flux->getRole());
        $fluxCotis->setMoyen(MoyenEnum::MOYEN_EMLC);
        $this->em->persist($fluxCotis);
        $this->operationUtils->executeOperations($fluxCotis);
    }

    /**
     * Method called to create Flux based on allowance amount (for household based allowance).
     */
    public function withdrawDownToTheCeiling(Adherent $adherent)
    {
        $balance = $adherent->getEmlcAccount()->getBalance();
        $ceiling = $adherent->getCeiling();
        $siege = $this->em->getRepository(Siege::class)->getTheOne();

        // get the amount we want to withdraw
        $amountDiff = $ceiling - $balance;

        if ($amountDiff >= 0) {
            throw new \Exception("Impossible de prélèver : le solde de l'adhérent est inférieur ou égal au plafond.");
        }

        $flux = new CotisationTavPrelevement();
        $flux->setExpediteur($adherent);
        $flux->setDestinataire($siege);
        $flux->setMontant(-$amountDiff);
        $flux->setReference("Prélèvement pour ramener le solde de " . $balance . "MonA sous le plafond de " . $ceiling . " MonA.");
        $flux->setOperateur($this->security->getUser());
        $flux->setRole($this->security->getUser()->getGroups()[0]->__toString());
        $flux->setMoyen(MoyenEnum::MOYEN_EMLC);
        $this->em->persist($flux);
        $this->operationUtils->executeOperations($flux);

        return $amountDiff;
    }

    /**
     * Get the last cotisation of an adhérent
     *
     * @param Adherent $adherent
     *
     * @return bool|date
     */
    public function getLastTavCotisationForAdherent(?Adherent $adherent)
    {
        $cotisations = [];
        if (null !== $adherent) {
            $cotisations = $this->em->getRepository(Flux::class)->getLastTavCotisation($adherent);
        }

        if (count($cotisations) > 0) {
            return $cotisations[0]["created_at"];
        }

        return false;
    }
}