<?php

namespace App\Entity;

use App\Entity\EntityTrait\EnablableEntityTrait;
use App\Flux\AccountInterface;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Ramsey\Uuid\Doctrine\UuidGenerator;

/**
 * @ORM\MappedSuperclass
 * @ORM\HasLifecycleCallbacks
 */
abstract class Account implements AccountInterface
{
    use EnablableEntityTrait;
    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;

    /**
     * @ORM\Column(name="balance", type="decimal", scale=2, options={"default": 0.00})
     */
    protected $balance = 0.00;

    /**
     * @var string
     *
     * @ORM\Column(name="currency", type="string", length=25, options={"default" : ""})
     */
    protected $currency = '';
    /**
     * 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';

    protected $operations;

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

    public function getBalance(): ?float
    {
        return $this->balance;
    }

    public function setBalance(float $balance): self
    {
        $this->balance = $balance;

        return $this;
    }

    /**
     * Can be negative amount.
     *
     * @param float $montant [description]
     */
    public function addAmount(float $montant): self
    {
        $this->balance += $montant;

        return $this;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }

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

        return $this;
    }

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

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

        return $this;
    }

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

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

    private function updateHash(LifecycleEventArgs $event): void
    {
        $account = $event->getEntity();
        if ($account->getBalance() < 0) {
            throw new \Exception('[ACCOUNT] Opération impossible ! Balance inférieure ou égal à zéro !');
        }
        // @TODO : generation du hash du account gourmand en ressource => voir pour optimiser ou le faire en async !
        $hash = password_hash($account->getAllInfosUncrypted(), PASSWORD_BCRYPT, ['cost' => 5]);
        $account->setHash($hash);
    }

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

    /**
     * Get operations.
     *
     * @return Operation[]|ArrayCollection
     */
    public function getOperations()
    {
        return $this->operations;
    }

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

        return $this;
    }

    /**
     * @param Operation $operation
     *
     * @return $this
     */
    public function addOperation(Operation $operation)
    {
        if (!$this->operations->contains($operation)) {
            $this->operations[] = $operation;
            $operation->setAccount($this);
        }

        return $this;
    }

    /**
     * @param Operation $operation
     *
     * @return $this
     */
    public function removeOperation(Operation $operation)
    {
        if ($this->operations->contains($operation)) {
            $this->operations->removeElement($operation);
            $operation->setAccount(null);
        }

        return $this;
    }
}
