<?php

declare(strict_types=1);

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\User\Model;

use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Product\Model\ProductPriceGroup;
use CpCreation\VitiCore\User\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'user_user')]
#[ORM\Index(
    name: 'user_expired_registration_purge_idx',
    columns: ['email_verification_expires_at', 'id'],
    options: ['where' => 'is_verified = FALSE AND email_verification_expires_at IS NOT NULL'],
)]
#[UniqueEntity(fields: ['email'], message: 'Cet email est déjà enregistré en base.')]
#[ORM\HasLifecycleCallbacks]
class User implements UserInterface, HasTimestamp, PasswordAuthenticatedUserInterface
{
    use Timestamp;

    #[ORM\Id]
    #[ORM\Column(type: 'uuid')]
    #[ORM\GeneratedValue(strategy: 'NONE')]
    private UuidInterface $id;

    #[ORM\Column(type: 'json')]
    #[Assert\NotBlank]
    private array $roles = [];

    #[ORM\Column(type: 'string', length: 60, unique: true)]
    #[Assert\NotBlank]
    #[Assert\Length(max: 60)]
    #[Assert\Email]
    private ?string $email = null;

    #[Assert\NotBlank(groups: ['registration'])]
    private ?string $plainPassword = null;

    #[ORM\Column(type: 'string')]
    private ?string $password = null;

    #[ORM\Column(type: 'boolean', options: ['default' => false])]
    private bool $marketingOptIn = false;

    #[ORM\Column(type: 'boolean', options: ['default' => true])]
    private bool $isVerified = false;

    #[ORM\Column(type: 'string', length: 128, nullable: true, unique: true)]
    private ?string $emailVerificationToken = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $emailVerificationRequestedAt = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $emailVerificationExpiresAt = null;

    #[ORM\Column(type: 'string', length: 128, nullable: true, unique: true)]
    private ?string $resetPasswordToken = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $resetPasswordRequestedAt = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $resetPasswordExpiresAt = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $lastLinkRequestedAt = null;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $lastLoginAt = null;

    #[ORM\Column(type: 'string', length: 45, nullable: true)]
    private ?string $lastLoginIp = null;

    /**
     * @var Collection<int, Cart>
     */
    #[ORM\OneToMany(targetEntity: Cart::class, mappedBy: 'user')]
    private Collection $carts;

    #[ORM\OneToOne(targetEntity: InvoiceAddress::class, cascade: ['all'])]
    #[ORM\JoinColumn(nullable: true)]
    private ?InvoiceAddress $invoiceAddress = null;

    #[ORM\OneToOne(targetEntity: DeliveryAddress::class, cascade: ['all'])]
    #[ORM\JoinColumn(nullable: true)]
    private ?DeliveryAddress $deliveryAddress = null;

    #[ORM\ManyToOne(targetEntity: ProductPriceGroup::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?ProductPriceGroup $group = null;

    public function __construct()
    {
        $this->id = Uuid::uuid4();
        $this->carts = new ArrayCollection();
    }

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

    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): void
    {
        $this->roles = array_values(array_unique(array_filter(
            $roles,
            static fn (mixed $role): bool => \is_string($role) && '' !== $role
        )));
    }

    public function getAssignableRoles(): array
    {
        return array_values(array_filter(
            $this->roles,
            static fn (string $role): bool => 'ROLE_USER' !== $role
        ));
    }

    public function setAssignableRoles(array $roles): void
    {
        $this->setRoles(array_values(array_filter(
            $roles,
            static fn (mixed $role): bool => \is_string($role) && '' !== $role && 'ROLE_USER' !== $role
        )));
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function getUsername(): string
    {
        return $this->email ?? '';
    }

    public function setEmail(?string $email): void
    {
        $this->email = $email;
    }

    public function setUsername(?string $username): void
    {
        $this->email = $username;
    }

    public function getUserIdentifier(): string
    {
        return $this->email ?? '';
    }

    public function getPlainPassword(): ?string
    {
        return $this->plainPassword;
    }

    public function setPlainPassword(?string $plainPassword): void
    {
        $this->plainPassword = $plainPassword;
    }

    public function getPassword(): ?string
    {
        return $this->password;
    }

    public function setPassword(?string $password): void
    {
        $this->password = $password;
    }

    public function isMarketingOptIn(): bool
    {
        return $this->marketingOptIn;
    }

    public function setMarketingOptIn(bool $marketingOptIn): void
    {
        $this->marketingOptIn = $marketingOptIn;
    }

    public function isVerified(): bool
    {
        return $this->isVerified;
    }

    public function setIsVerified(bool $isVerified): void
    {
        $this->isVerified = $isVerified;
    }

    public function getEmailVerificationToken(): ?string
    {
        return $this->emailVerificationToken;
    }

    public function setEmailVerificationToken(?string $emailVerificationToken): void
    {
        $this->emailVerificationToken = $emailVerificationToken;
    }

    public function getEmailVerificationRequestedAt(): ?\DateTimeInterface
    {
        return $this->emailVerificationRequestedAt;
    }

    public function setEmailVerificationRequestedAt(?\DateTimeInterface $emailVerificationRequestedAt): void
    {
        $this->emailVerificationRequestedAt = $emailVerificationRequestedAt;
    }

    public function getEmailVerificationExpiresAt(): ?\DateTimeInterface
    {
        return $this->emailVerificationExpiresAt;
    }

    public function setEmailVerificationExpiresAt(?\DateTimeInterface $emailVerificationExpiresAt): void
    {
        $this->emailVerificationExpiresAt = $emailVerificationExpiresAt;
    }

    public function getResetPasswordToken(): ?string
    {
        return $this->resetPasswordToken;
    }

    public function setResetPasswordToken(?string $resetPasswordToken): void
    {
        $this->resetPasswordToken = $resetPasswordToken;
    }

    public function getResetPasswordRequestedAt(): ?\DateTimeInterface
    {
        return $this->resetPasswordRequestedAt;
    }

    public function setResetPasswordRequestedAt(?\DateTimeInterface $resetPasswordRequestedAt): void
    {
        $this->resetPasswordRequestedAt = $resetPasswordRequestedAt;
    }

    public function getResetPasswordExpiresAt(): ?\DateTimeInterface
    {
        return $this->resetPasswordExpiresAt;
    }

    public function setResetPasswordExpiresAt(?\DateTimeInterface $resetPasswordExpiresAt): void
    {
        $this->resetPasswordExpiresAt = $resetPasswordExpiresAt;
    }

    public function eraseCredentials(): void
    {
    }

    /**
     * @return Collection<int, Cart>
     */
    public function getCarts(): Collection
    {
        return $this->carts;
    }

    public function addCart(Cart $cart): void
    {
        if (!$this->carts->contains($cart)) {
            $this->carts->add($cart);
            $cart->setUser($this);
        }
    }

    public function removeCart(Cart $cart): void
    {
        if ($this->carts->removeElement($cart)) {
            if ($cart->getUser() === $this) {
                $cart->setUser(null);
            }
        }
    }

    public function getInvoiceAddress(): ?InvoiceAddress
    {
        return $this->invoiceAddress;
    }

    public function setInvoiceAddress(?InvoiceAddress $invoiceAddress): void
    {
        $this->invoiceAddress = $invoiceAddress;
    }

    public function getLastLinkRequestedAt(): ?\DateTimeInterface
    {
        return $this->lastLinkRequestedAt;
    }

    public function setLastLinkRequestedAt(?\DateTimeInterface $lastLinkRequestedAt): void
    {
        $this->lastLinkRequestedAt = $lastLinkRequestedAt;
    }

    public function getLastLoginAt(): ?\DateTimeInterface
    {
        return $this->lastLoginAt;
    }

    public function setLastLoginAt(?\DateTimeInterface $lastLoginAt): void
    {
        $this->lastLoginAt = $lastLoginAt;
    }

    public function getLastLoginIp(): ?string
    {
        return $this->lastLoginIp;
    }

    public function setLastLoginIp(?string $lastLoginIp): void
    {
        $this->lastLoginIp = $lastLoginIp;
    }

    public function getDeliveryAddress(): ?DeliveryAddress
    {
        return $this->deliveryAddress;
    }

    public function setDeliveryAddress(?DeliveryAddress $deliveryAddress): void
    {
        $this->deliveryAddress = $deliveryAddress;
    }

    public function getGroup(): ?ProductPriceGroup
    {
        return $this->group;
    }

    public function setGroup(?ProductPriceGroup $group): void
    {
        $this->group = $group;
    }
}
