<?php

namespace App\Entity;

use App\Flux\AccountInterface;
use App\Flux\FluxInterface;
use App\Flux\OperationInterface;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Gamez\Symfony\Component\Serializer\Normalizer\UuidNormalizer;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Ramsey\Uuid\Doctrine\UuidGenerator;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\MappedSuperclass
 * @ORM\HasLifecycleCallbacks
 */
abstract class Operation implements OperationInterface
{
    use TimeStampableEntity;

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

    /**
     * @var float
     *
     * @ORM\Column(name="montant", type="decimal", scale=2)
     * @Assert\NotBlank
     * @Assert\Type("numeric")
     */
    protected $montant;

    /**
     * @var string
     *
     * @ORM\Column(name="currency", type="string", length=25, options={"default" : ""})
     */
    protected $currency = '';

    /**
     * @var string|null
     *
     * @ORM\Column(type="json", nullable=true)
     */
    protected $fluxData;

    /**
     * @var string|null
     *
     * @ORM\Column(type="json", nullable=true)
     */
    protected $extraData;

    protected $account;

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

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

    public function __construct()
    {
        $this->historical = false;
    }

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

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

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

        return $this;
    }

    /**
     * Get flux.
     *
     * @return
     */
    public function getFlux(): ?FluxInterface
    {
        return $this->flux;
    }

    /**
     * Set flux.
     *
     * @return $this
     */
    public function setFlux(FluxInterface $flux)
    {
        $this->flux = $flux;

        return $this;
    }

    /**
     * Get currency.
     *
     * @return string
     */
    public function getCurrency(): string
    {
        return $this->currency;
    }

    /**
     * Set currency.
     *
     * @return $this
     */
    public function setCurrency(string $currency): self
    {
        $this->currency = $currency;

        return $this;
    }

    public function getAccount(): ?AccountInterface
    {
        return $this->account;
    }

    public function setAccount(AccountInterface $account): self
    {
        $this->account = $account;

        return $this;
    }

    public function getHash(): ?string
    {
        return $this->hash;
    }

    public function setHash(string $hash): self
    {
        $this->hash = $hash;

        return $this;
    }

    /**
     * Get fluxData.
     *
     * @return
     */
    public function getFluxData()
    {
        return $this->fluxData;
    }

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

        return $this;
    }

    /**
     * Get extraData.
     *
     * @return
     */
    public function getExtraData()
    {
        return $this->extraData;
    }

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

        return $this;
    }

    /**
     * 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;
    }

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

    protected function updateFluxData(LifecycleEventArgs $event): void
    {
        $flux = $event->getEntity()->getFlux();
        // Serialize form data in json to store with payment object and persist when payment is valid
        $encoders = [new JsonEncoder()];
        $normalizers = [new UuidNormalizer(), new DateTimeNormalizer(), new GetSetMethodNormalizer()];
        // $normalizers = [new ObjectNormalizer()];
        $serializer = new Serializer($normalizers, $encoders);
        $data = $serializer->normalize(
            $flux,
            null,
            [
                DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s',
                AbstractNormalizer::ATTRIBUTES => [
                    'id', 'role', 'parenttype', 'type', 'montant', 'moyen', 'tauxreconversion', 'reference', 'hash', 'createdAt', 'data',
                    'operateur' => ['id', 'email', 'firstname', 'lastname'],
                    'destinataire' => ['id', 'name'],
                    'expediteur' => ['id', 'name'],
                ],
            ]
        );
        $fluxData = $serializer->serialize($data, 'json');
        $event->getEntity()->setFluxData($fluxData);
        $event->getEntityManager()->persist($event->getEntity());
        $event->getEntityManager()->flush();
    }

    protected function updateHash(LifecycleEventArgs $event): void
    {
        $operation = $event->getEntity();
        // @TODO : generation du hash du operation gourmand en ressource => voir pour optimiser ou le faire en async !
        $hash = password_hash($operation->getAllInfosUncrypted(), PASSWORD_BCRYPT, ['cost' => 5]);
        $operation->setHash($hash);
        $event->getEntityManager()->persist($operation);
        $event->getEntityManager()->flush();
    }

    public function getAllInfosUncrypted()
    {
        return $_ENV['APP_SECRET'] . $this->getId() . (0 == $this->getMontant() ? 0 : $this->getMontant()) . $this->getCurrency();
    }
}
