<?php

namespace App\Entity;

use App\Enum\MoyenEnum;
use App\Flux\FluxInterface;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Ramsey\Uuid\Doctrine\UuidGenerator;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * FLUX = TRANSFERT ou TRANSACTION ou COTISATION ou RECONVERSION ou RETRAIT ou VENTE ou ACHAT MONNAIE.
 *
 * @ORM\Entity(repositoryClass="App\Repository\FluxRepository")
 * @ORM\HasLifecycleCallbacks()
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({
 *     "achat_monnaie" = "AchatMonnaie",
 *     "achat_monnaie_adherent" = "AchatMonnaieAdherent",
 *     "achat_monnaie_prestataire" = "AchatMonnaiePrestataire",
 *     "change" = "Change",
 *     "chg_pre_cpt" = "ChangePrestataireComptoir",
 *     "chg_adh_cpt" = "ChangeAdherentComptoir",
 *     "cotisation" = "Cotisation",
 *     "cotisation_adherent" = "CotisationAdherent",
 *     "cotisation_prestataire" = "CotisationPrestataire",
 *     "don" = "Don",
 *     "don_adherent" = "DonAdherent",
 *     "don_prestataire" = "DonPrestataire",
 *     "retrait" = "Retrait",
 *     "ret_cpt_adh" = "RetraitComptoirAdherent",
 *     "ret_cpt_pre" = "RetraitComptoirPrestataire",
 *     "transaction" = "Transaction",
 *     "tro_adh_pre" = "TransactionAdherentPrestataire",
 *     "tro_adh_adh" = "TransactionAdherentAdherent",
 *     "tro_pre_adh" = "TransactionPrestataireAdherent",
 *     "tro_pre_pre" = "TransactionPrestatairePrestataire",
 *     "transfert" = "Transfert",
 *     "tre_cpt_grp" = "TransfertComptoirGroupe",
 *     "tre_grp_cpt" = "TransfertGroupeComptoir",
 *     "tre_pre_sie" = "Reconversion",
 *     "tre_sie_grp" = "TransfertSiegeGroupe",
 *     "tre_grp_sie" = "TransfertGroupeSiege",
 *     "vente" = "Vente",
 *     "vte_cpt_pre" = "VenteComptoirPrestataire",
 *     "vte_cpt_adh" = "VenteComptoirAdherent",
 *     "venteemlc" = "VenteEmlc",
 *     "vte_emlc_cpt_adh" = "VenteEmlcComptoirAdherent",
 *     "vte_emlc_cpt_pre" = "VenteEmlcComptoirPrestataire",
 *     "ticket_fix" = "TicketFix",
 *     "ticket_fix_print" = "TicketFixPrint",
 *     "ticket_fix_destroy" = "TicketFixDestroy",
 *     "application_cotisation_tav" = "CotisationTavApplication",
 *     "reversement_cotisation_adherent" = "CotisationTavReversement",
 *     "prelevement_cotisation_adherent" = "CotisationTavPrelevement",
 *     "prelevement_cotisation_adherent_depassement_plafond" = "CotisationTavPrelevementDepassementPlafond",
 *     "prelevement_cotisation_adherent_correction_solde" = "CotisationTavPrelevementCorrectionSolde",
 *     "reversement_cotisation_adherent_correction_solde" = "CotisationTavReversementCorrectionSolde",
 * })
 */
abstract class Flux implements FluxInterface
{
    use TimestampableEntity;

    const TYPE_ACHAT = 'achat';
    const TYPE_CHANGE = 'change';
    const TYPE_COTISATION = 'cotisation';
    const TYPE_DON = 'don';
    const TYPE_RECONVERSION = 'reconversion';
    const TYPE_RETRAIT = 'retrait';
    const TYPE_TRANSACTION = 'transaction';
    const TYPE_TRANSFERT = 'transfert';
    const TYPE_VENTE = 'vente';
    const TYPE_VENTE_EMLC = 'vente_emlc';
    const TYPE_TICKET_FIX = 'ticket_fix';
    const TYPE_APPLICATION_COTISATION_TAV = 'application_cotisation_tav';

    /**
     * @var \Ramsey\Uuid\UuidInterface
     *
     * @ORM\Id
     * @ORM\Column(type="uuid", unique=true)
     * @ORM\GeneratedValue(strategy="CUSTOM")
     * @ORM\CustomIdGenerator(class=UuidGenerator::class)
     * @Groups({"read"})
     */
    protected $id;

    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="flux")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
     * @Groups({"read", "write"})
     */
    protected $operateur;

    /**
     * Role en cours de l'utilisateur.
     *
     * @var string
     *
     * @ORM\Column(name="role", type="string", length=200, nullable=true)
     * @Groups({"read", "write"})
     */
    protected $role;

    /**
     * Type de transfert / transaction : exemple : Prestataire à Adhérent.
     *
     * @var string
     *
     * @ORM\Column(name="type", type="string", length=200)
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    protected $type;

    /**
     * Type de flux : transfert / transaction.
     *
     * @var string
     *
     * @ORM\Column(name="parenttype", type="string", length=30)
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    protected $parenttype;

    /**
     * @var float
     *
     * @ORM\Column(name="montant", type="decimal", scale=2)
     * @Assert\NotBlank
     * @Assert\Type("numeric")
     * @Assert\GreaterThanOrEqual(
     *     value = 0
     * )
     * @Groups({"read", "write"})
     */
    protected $montant;

    /**
     * @var string
     *
     * @ORM\Column(name="moyen", type="string", length=100)
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    private $moyen;

    /**
     * @var string|null
     *
     * @ORM\Column(name="reference", type="string", length=255, nullable=true)
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    protected $reference;

    /**
     * Hash => permet de vérifier l'intégrité des données.
     *
     * @var text
     *
     * @ORM\Column(name="hash", type="text", options={"default" : "tmp"})
     * @Groups({"read"})
     */
    protected $hash = 'tmp';

    /**
     * @var float
     *
     * @ORM\Column(name="tauxreconversion", type="decimal", precision=7, scale=2, nullable=true)
     * @Groups({"read", "write"})
     */
    protected $tauxreconversion;

    /**
     * @var float
     *
     * @ORM\Column(name="data", type="array", nullable=true)
     * @Groups({"read", "write"})
     */
    protected $data;

    /**
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    protected $expediteur = null;

    /**
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    protected $destinataire = null;

    /**
     * @Groups({"read", "write"})
     */
    protected $cotisationInfos = null;

    /**
     * @var ArrayCollection|OperationAdherent[]
     * @ORM\OneToMany(targetEntity=OperationAdherent::class, mappedBy="flux")
     */
    protected $operationsAdherent;

    /**
     * @var ArrayCollection|OperationPrestataire[]
     * @ORM\OneToMany(targetEntity=OperationPrestataire::class, mappedBy="flux")
     */
    protected $operationsPrestataire;

    /**
     * @var ArrayCollection|OperationComptoir[]
     * @ORM\OneToMany(targetEntity=OperationComptoir::class, mappedBy="flux")
     */
    protected $operationsComptoir;

    /**
     * @var ArrayCollection|OperationGroupe[]
     * @ORM\OneToMany(targetEntity=OperationGroupe::class, mappedBy="flux")
     */
    protected $operationsGroupe;

    /**
     * @var ArrayCollection|OperationSiege[]
     * @ORM\OneToMany(targetEntity=OperationSiege::class, mappedBy="flux")
     */
    protected $operationsSiege;

    /**
     * @var string|null
     *
     * @Assert\Type("bool")
     * @ORM\Column(name="historical", type="boolean", nullable=false, options={"default" : false})
     */
    protected $historical;

    /**
     * @ORM\OneToOne(targetEntity=Allocation::class, mappedBy="cotisationFlux")
     */
    private $allocation;

    abstract public function getParenttype(): string;

    abstract public function getType(): string;

    /**
     * Retourne la liste des opérations à effectuer.
     *
     * @return array Tableau d'operations
     */
    abstract public function getAllOperations($em);

    /**
     * @deprecated since v2.1.4
     */
    abstract public function operate($em);

    /**
     * Obtenir la liste des utilisateurs à notifier.
     *
     * @return array Tableau d'utilisateurs
     */
    abstract public function getUsersToNotify();

    public function __construct()
    {
        $this->parenttype = $this->getParenttype();
        $this->type = $this->getType();
        $this->historical = false;
    }

    public function getId()
    {
        return $this->id;
    }

    /**
     * @param $parenttype
     *
     * @return $this
     */
    public function setParenttype($parenttype)
    {
        $this->parenttype = $parenttype;

        return $this;
    }

    /**
     * @param User $destinataire
     *
     * @return $this
     */
    public function setOperateur(?User $operateur)
    {
        $this->operateur = $operateur;

        return $this;
    }

    /**
     * @return User operateur
     */
    public function getOperateur(): ?User
    {
        return $this->operateur;
    }

    /**
     * @param $destinataire
     *
     * @return $this
     */
    public function setDestinataire($destinataire)
    {
        $this->destinataire = $destinataire;

        return $this;
    }

    /**
     * @return destinataire
     */
    public function getDestinataire()
    {
        return $this->destinataire;
    }

    /**
     * @param $expediteur
     *
     * @return $this
     */
    public function setExpediteur($expediteur)
    {
        $this->expediteur = $expediteur;

        return $this;
    }

    /**
     * @return expediteur
     */
    public function getExpediteur()
    {
        return $this->expediteur;
    }

    /**
     * @param string $type
     *
     * @return Transaction
     */
    public function setType(string $type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * @return string
     */
    public function getRole(): ?string
    {
        return $this->role;
    }

    /**
     * @param string $role
     *
     * @return Transaction
     */
    public function setRole(?string $role)
    {
        $this->role = $role;

        return $this;
    }

    /**
     * @return float
     */
    public function getMontant(): ?float
    {
        return number_format($this->montant, 2, '.', '');
    }

    /**
     * @param float $montant
     *
     * @return Transaction
     */
    public function setMontant(float $montant)
    {
        $this->montant = $montant;

        return $this;
    }

    /**
     * @return string
     */
    public function getReference(): ?string
    {
        return $this->reference;
    }

    /**
     * @param string $reference
     *
     * @return Transaction
     */
    public function setReference(string $reference)
    {
        $this->reference = $reference;

        return $this;
    }

    /**
     * Get hash.
     *
     * @return text
     */
    public function getHash()
    {
        return $this->hash;
    }

    /**
     * Set hash.
     *
     * @return $this
     */
    public function setHash($hash)
    {
        $this->hash = $hash;

        return $this;
    }

    /**
     * @return string
     */
    public function getMoyen(): ?string
    {
        return $this->moyen;
    }

    public function setMoyen($moyen)
    {
        if (!in_array($moyen, MoyenEnum::getAvailableTypes())) {
            throw new \InvalidArgumentException('Moyen de paiement invalide !');
        }
        $this->moyen = $moyen;

        return $this;
    }

    /**
     * Get tauxreconversion.
     *
     * @return
     */
    public function getTauxreconversion(): ?float
    {
        return $this->tauxreconversion;
    }

    public function getTauxreconversionpercent(): ?float
    {
        return $this->tauxreconversion / 100;
    }

    /**
     * Set tauxreconversion.
     *
     * @return $this
     */
    public function setTauxreconversion(?float $tauxreconversion): self
    {
        $this->tauxreconversion = $tauxreconversion;

        return $this;
    }

    /**
     * Get data.
     *
     * @return
     */
    public function getData(): ?array
    {
        return $this->data;
    }

    /**
     * Set data.
     *
     * @return $this
     */
    public function setData(?array $data)
    {
        $this->data = $data;

        return $this;
    }

    public function isValidationAchat()
    {
        if (null !== $this->getData()) {
            if (isset($this->data['validationAchat']) && true == $this->data['validationAchat']) {
                return true;
            }
        }

        return false;
    }

    public function getMontantareconvertir()
    {
        return round($this->getMontant() - ($this->getMontant() * ($this->getTauxreconversion() / 100)), 2);
    }

    public function getMontantcommission()
    {
        return round(($this->getMontant() - $this->getMontantareconvertir()), 2);
    }

    public function isVente()
    {
        return false;
    }

    /**
     * Is historical ?
     *
     * @return bool
     */
    public function isHistorical(): bool
    {
        return $this->historical;
    }

    /**
     * Get historical.
     *
     * @return
     */
    public function getHistorical(): bool
    {
        return $this->historical;
    }

    /**
     * Set historical.
     *
     * @return $this
     */
    public function setHistorical($historical): self
    {
        $this->historical = $historical;

        return $this;
    }

    public function getVerify()
    {
        if (null == $this->getHash()) {
            return 'Vide';
        }

        return (true === password_verify($this->getAllInfosUncrypted(), $this->getHash())) ? 'Oui' : 'Non';
    }

    /**
     * @ORM\PrePersist
     *
     * @param LifecycleEventArgs $event
     */
    public function prePersist(LifecycleEventArgs $event)
    {
        $flux = $event->getEntity();
        if (empty($flux->getExpediteur())) {
            throw new \Exception("Opération impossible ! Pas d'expéditeur !");
        }
        if ($flux->getMontant() <= 0 && !(self::TYPE_COTISATION == $flux->getParenttype() || self::TYPE_DON == $flux->getParenttype())) {
            throw new \Exception('Opération impossible ! Montant inférieur ou égal à zéro !');
        }
        if ($flux->getExpediteur() == $flux->getDestinataire()) {
            throw new \Exception("Opération impossible ! L'expéditeur et le destinataire ne peuvent pas être similaire !");
        }
        // $flux->setHash('tmp');
    }

    /**
     * @ORM\PostPersist
     *
     * @param LifecycleEventArgs $event
     */
    public function postPersist(LifecycleEventArgs $event): void
    {
        $this->updateHash($event);
    }

    private function updateHash(LifecycleEventArgs $event): void
    {
        $flux = $event->getEntity();
        // if (empty($flux->getExpediteur())) {
        //     throw new \Exception("[FLUX] Opération impossible ! Pas d'expéditeur !");
        // }
        // if ($flux->getMontant() <= 0 && self::TYPE_COTISATION !== $flux->getParenttype()) {
        //     throw new \Exception('[FLUX] Opération impossible ! Montant inférieur ou égal à zéro !');
        // }
        // if ($flux->getExpediteur() == $flux->getDestinataire()) {
        //     throw new \Exception('[FLUX] Opération impossible ! Expéditeur et Destinataire ne peuvent pas être les mêmes !');
        // }
        // @TODO : generation du hash du flux gourmand en ressource => voir pour optimiser ou le faire en async !
        $hash = password_hash($this->getAllInfosUncrypted(), PASSWORD_BCRYPT, ['cost' => 5]);
        $flux->setHash($hash);
        $event->getEntityManager()->persist($flux);
        $event->getEntityManager()->flush();
    }

    public function getAllInfosUncrypted(): string
    {
        return $_ENV['APP_SECRET'] .
            $this->getId() .
            ($this->getOperateur() ? $this->getOperateur()->getId() : '') .
            $this->getRole() .
            $this->getType() .
            (0 == $this->getMontant() ? 0 : $this->getMontant()) .
            $this->getMoyen() .
            $this->getReference() .
            ($this->getDestinataire() ? $this->getDestinataire()->getId() : '') .
            ($this->getExpediteur() ? $this->getExpediteur()->getId() : '');
    }

    public function getOperateurAndRole(): string
    {
        return $this->getOperateur() ? ($this->getOperateur()->__toString() . ' (' . $this->getRole() . ')') : '';
    }

    public function __toString(): string
    {
        if (empty($this->getDestinataire()) || empty($this->getExpediteur()) || null === $this->getMontant()) {
            return '[FLUX] Visualisation impossible ! Destinataire / Expéditeur et/ou montant manquant(s) !';
        }

        return ($this->getCreatedAt() ? $this->getCreatedAt()->format('d/m/Y H:i') . ' | ' : '') . ucwords($this->getParenttype()) . ' : ' . $this->getExpediteur() . ' => ' . $this->getDestinataire() . ' : ' . $this->getMontant() . '€';
    }

    public function ssaFriendlyTypeName()
    {
        $friendlyTypeNames = Flux::ssaUsefulFriendlyTypeNames();
        return array_key_exists($this->type,$friendlyTypeNames) ?
            $friendlyTypeNames[$this->type]
            : $this->type;
    }

    public static function ssaUsefulFriendlyTypeNames($uniqueValues = false)
    {
        return [
            VenteEmlc::TYPE_VENTE_EMLC_ADHERENT => "Achat de MonA via cotisation" . ($uniqueValues ? " (au comptoir)" : ""),
            AchatMonnaie::TYPE_ACHAT_ADHERENT => "Achat de MonA via cotisation" . ($uniqueValues ? " (via Payzen)" : ""),
            CotisationTavApplication::TYPE_REVERSEMENT_COTISATION_ADHERENT => "Allocation complémentaire de la caisse",
            CotisationTavApplication::TYPE_REVERSEMENT_COTISATION_ADHERENT_CORRECTION_SOLDE => "Allocation complémentaire de la caisse" . ($uniqueValues ? " (correction solde)" : ""),
            CotisationTavApplication::TYPE_PRELEVEMENT_COTISATION_ADHERENT => "Réduction de l'allocation",
            CotisationTavApplication::TYPE_PRELEVEMENT_COTISATION_ADHERENT_DEPASSEMENT_PLAFOND => "Réduction de l'allocation" . ($uniqueValues ? " (dépassement plafond)" : ""),
            CotisationTavApplication::TYPE_PRELEVEMENT_COTISATION_ADHERENT_CORRECTION_SOLDE => "Réduction de l'allocation" . ($uniqueValues ? " (correction solde)" : ""),
            Don::TYPE_DON_ADHERENT => Don::TYPE_DON_ADHERENT,
            Don::TYPE_DON_PRESTATAIRE => Don::TYPE_DON_PRESTATAIRE,
            Reconversion::TYPE_RECONVERSION_PRESTATAIRE => Reconversion::TYPE_RECONVERSION_PRESTATAIRE,
            Transaction::TYPE_TRANSACTION_PRESTATAIRE_ADHERENT => Transaction::TYPE_TRANSACTION_PRESTATAIRE_ADHERENT,
            Transaction::TYPE_TRANSACTION_ADHERENT_PRESTATAIRE => Transaction::TYPE_TRANSACTION_ADHERENT_PRESTATAIRE
        ];
    }

    public function getAllocation(): ?Allocation
    {
        return $this->allocation;
    }

    public function setAllocation(Allocation $allocation): self
    {
        // set the other side of the relation if necessary
        if (
            ($this->type == 'vente_emlc_adherent' || $this->type ==  'achat_monnaie_adherent')
            && $allocation->getCotisationFlux() !== $this
        ) {
            $allocation->setCotisationFlux($this);
        } else if (
            ($this->type == 'prelevement_cotisation_adherent' || $this->type ==  'reversement_cotisation_adherent')
            && $allocation->complementaryFlux() !== $this
        ) {
            $allocation->complementaryFlux($this);
        }

        $this->allocation = $allocation;

        return $this;
    }
}
