<?php declare(strict_types=1);
/*
 * This file is part of the SplendidBear Websites' projects.
 *
 * Copyright (c) 2026 @ www.splendidbear.org
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Entity;

use App\Repository\UserRepository;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Table;
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration;
use Scheb\TwoFactorBundle\Model\Totp\TotpConfigurationInterface;
use Scheb\TwoFactorBundle\Model\Totp\TwoFactorInterface as TotpTwoFactorInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * Class User
 *
 * @package  App\Entity
 * @author   Lang <https://www.splendidbear.org>
 * @category Class
 * @license  https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
 * @link     www.splendidbear.org
 * @since    2026. 04. 09.
 */
#[Entity(repositoryClass: UserRepository::class), Table(name: 'app_user')]
#[UniqueEntity(fields: ['username'], message: 'This username is already taken.')]
#[UniqueEntity(fields: ['email'], message: 'This email address is already registered.')]
class User implements UserInterface, PasswordAuthenticatedUserInterface, TotpTwoFactorInterface, BackupCodeInterface
{
    #[Id, GeneratedValue, Column]
    public private(set) ?int $id = null;

    #[Column(length: 180, unique: true)]
    public ?string $username = null;

    #[Column]
    public array $roles = [];

    #[Column(nullable: true)]
    public ?string $password = null;

    #[Column(length: 254, unique: true, nullable: true)]
    public ?string $email = null;

    #[Column]
    public bool $isVerified = false;

    #[Column(length: 64, nullable: true)]
    public ?string $verificationToken = null;

    #[Column(length: 64, nullable: true)]
    public ?string $resetToken = null;

    #[Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    public ?DateTime $resetTokenExpiresAt = null;

    #[Column(length: 255, nullable: true)]
    public ?string $totpSecret = null;

    /** Stored as nullable JSON; the get hook always exposes a non-null array. */
    #[Column(type: Types::JSON, nullable: true)]
    public ?array $backupCodes = [] {
        get => $this->backupCodes ?? [];
    }

    #[Column(length: 255, nullable: true)]
    public ?string $avatarPath = null;

    #[Column(nullable: true)]
    public ?bool $consentGiven = null;


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

    public function getUserIdentifier(): string
    {
        return (string)$this->username;
    }

    public function getRoles(): array
    {
        return array_unique([...$this->roles, 'ROLE_USER']);
    }

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

    public function eraseCredentials(): void
    {
    }

    public function isTotpAuthenticationEnabled(): bool
    {
        return null !== $this->totpSecret;
    }

    public function getTotpAuthenticationUsername(): string
    {
        return $this->getUserIdentifier();
    }

    public function getTotpAuthenticationConfiguration(): ?TotpConfigurationInterface
    {
        if (null === $this->totpSecret) {
            return null;
        }
        return new TotpConfiguration($this->totpSecret, TotpConfiguration::ALGORITHM_SHA1, 30, 6);
    }

    public function isBackupCode(string $code): bool
    {
        return \in_array($code, $this->backupCodes, true);
    }

    public function invalidateBackupCode(string $code): void
    {
        $this->backupCodes = array_values(array_filter($this->backupCodes, fn($c) => $c !== $code));
    }
}
