<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2017 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\Entity\Webservice\Type;

use Doctrine\Common\Collections\ArrayCollection;
use Ramsey\Uuid\Uuid;
use Sso\WebserviceBundle\PasswordCrypt\PasswordCryptInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface as Validator;
use Sso\WebserviceBundle\Api\Exception\Type\Api as ApiException;

/**
 * Class User
 *
 * @copyright  2017 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 * @package    Sso\WebserviceBundle\Entity\Webservice\Type
 */
class User extends Base
{
    const PASSWORD_HASH_COST = 10;

    /**
     * Unique user id
     *
     * @var string
     */
    private $Id;

    /**
     * Fill this variable with Guid, Username or Email to find user
     *
     * @var string
     */
    private $Identifier;

    /**
     * Global unique user id
     *
     * @var string
     */
    private $Guid;

    /**
     * Email
     *
     * @var string|null
     */
    private $Email;

    /**
     * @var string|null
     */
    private $LowerEmail;

    /**
     * Username
     *
     * @var string
     */
    private $Username;

    /**
     * Username
     *
     * @var string
     */
    private $LowerUsername;

    /**
     * Hashed password
     *
     * @var string
     */
    private $Password;

    /**
     * Unhashed password (only available after setPassword())
     *
     * @var string|null
     */
    private $passwordPlain;

    /**
     * Plain previous password (not persistence, only available if set from current request)
     *
     * @var string|null
     */
    private $previousPassword;

    /**
     * Firstname
     *
     * @var string
     */
    private $Firstname;

    /**
     * Lastname
     *
     * @var string
     */
    private $Lastname;

    /**
     * Active flag
     *
     * @var boolean
     */
    private $Active = 0;

    /**
     * @var string
     */
    private $PasswordToken;

    /**
     * @var \DateTime
     */
    private $PasswordTokenExpire;

    /**
     * @var string
     */
    private $ActivateToken;

    /**
     * @var \DateTime
     */
    private $ActivateTokenExpire;

    /**
     * @var string
     */
    private $resetPasswordToken;

    /**
     * @var \DateTime
     */
    private $resetPasswordTokenExpire;

    /**
     * @var ArrayCollection|UserApplication[]
     */
    private $UserApplications;

    /**
     * @var string
     */
    private $AuthId;

    /**
     * @var string
     */
    private $LdapSearchAttributes;

    /**
     * @var string
     */
    private $LdapSearchValue;

    /**
     * @var boolean
     */
    private $Deleted = false;

    /**
     * @var \DateTime
     */
    private $DeletedAt;

    /**
     * @var \DateTime
     */
    private $CreatedAt;

    /**
     * @var \DateTime
     */
    private $UpdatedAt;

    /**
     * @var \DateTime
     */
    private $UpdatedReferencesAt;

    /**
     * @var \DateTime
     */
    private $LastLoginAt;

    /**
     * True, if mfa is enabled
     *
     * @var boolean
     */
    private $MfaEnabled = false;

    /**
     * True, if mfa secret should be recreated
     *
     * @var boolean
     */
    private $MfaRecreate = false;

    /**
     * Contains the mfa secret
     *
     * @var string
     */
    private $MfaSecret;

    /**
     * If user has received the mfa secret, this is set true
     *
     * @var boolean
     */
    private $MfaSecretReceived = false;

    /**
     * Mfa secret create date
     *
     * @var \DateTime
     */
    private $MfaCreatedAt;

    /**
     * Mfa secret update date
     *
     * @var \DateTime
     */
    private $MfaUpdatedAt;

    /**
     * True, if activate token should be recreated
     *
     * (Not persistent)
     *
     * @var boolean
     */
    private $ActivateTokenRecreate = false;

    /**
     * @var integer
     */
    private $LoginFails;

    /**
     * @var boolean
     */
    private $loginRequest = false;

    /**
     * @var null|\DateTime
     */
    private $loginFailedFirstAt = null;

    /**
     * @var null|\DateTime
     */
    private $loginFailedLastAt = null;

    /**
     * @var null|string
     */
    private $passwordPolicy = null;

    /**
     * password change date from before it was updated with the current date, not persisted, only used for validation
     *
     * @var null|\DateTime
     */
    private $previousLastPasswordChange = null;

    /**
     * @var null|\DateTime
     */
    private $lastPasswordChange = null;

    /**
     * @var boolean
     */
    private $passwordExpired;

    /**
     * @var string
     */
    private $passwordEncryptType;

    /**
     * @var ArrayCollection|UserGroup[]
     */
    private $userGroups;

    /**
     * Constructor
     *
     * @param Validator $valApi
     */
    public function __construct(Validator $valApi)
    {
        parent::__construct($valApi);
        $this->UserApplications = new ArrayCollection();
        $this->CreatedAt = new \DateTime();
        $this->UpdatedAt = new \DateTime();
        $this->userGroups = new ArrayCollection();
    }

    /**
     * Initialize object with new values
     * @param array|User $values
     * @param array|null $validationGroups Null for full validation, array of group-name(s) for group specific validation
     * @throws ApiException
     */
    public function init($values, $validationGroups = null)
    {
        // Generate new user-id
        $this->generateId();

        parent::init($values, $validationGroups);
    }

    /**
     * Generate unique user id
     *
     * @return string User-Id
     * @throws ApiException
     */
    public function generateId()
    {
        if (null !== $this->Id) {
            throw new ApiException('Changing the user-id is not allowed!');
        }
        return $this->Id = hash('sha256', mt_rand() . '#' . microtime());
    }

    /**
     * Get userId
     *
     * @return string
     */
    public function getId()
    {
        return $this->Id;
    }

    /**
     * Set Id
     *
     * @throws ApiException
     */
    public function setId($id)
    {
        if (null !== $this->Id && $id !== $this->Id) {
            throw new ApiException('It is not allowed, to change the user-id!');
        }
    }

    /**
     * Get Identifier
     *
     * @return string
     */
    public function getIdentifier()
    {
        return $this->Identifier;
    }

    /**
     * Set Identifier
     *
     * This identifier may be a Guid, username or email.
     * If you don't know, what it is, use this setter instead
     * of guid, username or email, to find user in database.
     * This value is not validated!
     *
     * @param string $Identifier
     * @return User
     */
    public function setIdentifier($Identifier)
    {
        $this->Identifier = $Identifier;

        return $this;
    }

    /**
     * Get Guid
     *
     * @return string
     */
    public function getGuid()
    {
        return $this->Guid;
    }

    /**
     * Set Guid
     *
     * @param string $guid
     * @return User
     */
    public function setGuid($guid)
    {
        $this->Guid = $guid;
        $this->_setChanged('Guid');

        return $this;
    }

    /**
     * Generate new guid
     *
     * @return string
     */
    public function generateGuid()
    {
        $guid = Uuid::uuid4()->toString();
        $this->setGuid($guid);
        return $guid;
    }

    /**
     * Get LowerEmail
     *
     * @return string|null
     */
    public function getLowerEmail()
    {
        return $this->LowerEmail;
    }

    /**
     * Get LowerUsername
     *
     * @return string
     */
    public function getLowerUsername()
    {
        return $this->LowerUsername;
    }

    /**
     * Get Password
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->Password;
    }

    /**
     * Set Password
     *
     * @param string|null $password
     * @return User
     */
    public function setPassword($password)
    {
        if (null !== $password) {
            $this->passwordPlain = (string)$password;
            $this->Password = null;
            $this->setLastPasswordChange(new \DateTime());
            $this->_setChanged('Password');
        }

        return $this;
    }

    /**
     * For user raw data update or import - use only if you really know what you are doing!
     *
     * @param string $password
     * @param string $encryptType
     */
    public function setEncryptedPassword(string $password, string $encryptType)
    {
        // Change flags for updated fields are not set - keep the entity valid!
        $this->passwordEncryptType = $encryptType;
        $this->Password = $password;
    }

    /**
     * Set new password and reset failed logins, reset-token
     *
     * @param string $password
     * @param PasswordCryptInterface $passwordCrypt
     */
    public function resetPassword($password)
    {
        $this->passwordPlain = (string)$password;
        $this->Password = null;
        $this->setLastPasswordChange(new \DateTime());
        $this->setPasswordToken(null);
        $this->setPasswordTokenExpire(null);
        $this->setResetPasswordToken(null);
        $this->setResetPasswordTokenExpire(null);
        $this->setLoginFails(0);
        $this->setPasswordExpired(false);
    }

    /**
     * @param PasswordCryptInterface $passwordCrypt
     */
    public function encryptPassword(PasswordCryptInterface $passwordCrypt)
    {
        if (null !== $this->passwordPlain) {
            $this->passwordEncryptType = $passwordCrypt->getEncryptType();
            $this->Password = $passwordCrypt->passwordHash($this->passwordPlain);
        }
    }

    /**
     * Verify if a given password is correct for this user object
     *
     * @param $password
     * @param PasswordCryptInterface $passwordCrypt
     * @return bool
     */
    public function verifyPassword($password, PasswordCryptInterface $passwordCrypt)
    {
        return $passwordCrypt->passwordVerify($password, $this->Password);
    }

    /**
     * Get Username
     *
     * @return string
     */
    public function getUsername()
    {
        return $this->Username;
    }

    /**
     * Set Username
     *
     * @param string $Username
     * @return User
     */
    public function setUsername($Username)
    {
        $this->Username = $Username;
        $this->LowerUsername = strtolower($Username);
        $this->_setChanged('Username');

        return $this;
    }

    /**
     * Get Email
     *
     * @return string|null
     */
    public function getEmail()
    {
        return $this->Email;
    }

    /**
     * Set Email
     *
     * @param string|null $Email
     * @return User
     */
    public function setEmail($Email)
    {
        if (empty($Email)) {
            $this->Email = null;
            $this->LowerEmail = null;
        } else {
            $this->Email = $Email;
            $this->LowerEmail = strtolower($Email);
        }
        $this->_setChanged('Email');

        return $this;
    }

    /**
     * Get plain password string
     *
     * @return string
     */
    public function getPlainPassword()
    {
        return null !== $this->passwordPlain ? $this->passwordPlain : '';
    }

    /**
     * Plain previous password (not persistence, only available if set from current request)
     *
     * @return string|null
     */
    public function getPreviousPassword()
    {
        return $this->previousPassword;
    }

    /**
     * Set plain previous password (not persistence)
     *
     * @param string|null $previousPassword
     * @return User
     */
    public function setPreviousPassword($previousPassword)
    {
        $this->previousPassword = $previousPassword;
        return $this;
    }

    /**
     * Get Firstname
     *
     * @return string
     */
    public function getFirstname()
    {
        return $this->Firstname;
    }

    /**
     * Set Firstname
     *
     * @param string $Firstname
     * @return User
     */
    public function setFirstname($Firstname)
    {
        $this->Firstname = $Firstname;
        $this->_setChanged('Firstname');

        return $this;
    }

    /**
     * Get Lastname
     *
     * @return string
     */
    public function getLastname()
    {
        return $this->Lastname;
    }

    /**
     * Set Lastname
     *
     * @param string $Lastname
     * @return User
     */
    public function setLastname($Lastname)
    {

        $this->Lastname = $Lastname;
        $this->_setChanged('Lastname');

        return $this;
    }

    /**
     * @return bool
     */
    public function isActive()
    {
        return $this->Active;
    }

    /**
     * Get Active
     *
     * @return boolean
     */
    public function getActive()
    {
        return empty($this->Active) ? "0" : "1";
    }

    /**
     * Set Active
     *
     * @param boolean $Active
     * @return User
     */
    public function setActive($Active)
    {
        $this->Active = (bool)$Active;
        if ($Active) {
            $this->clearActivateRequestToken();
        }
        $this->_setChanged('Active');

        return $this;
    }

    /**
     * Delete activate request token and token expire
     */
    public function clearActivateRequestToken()
    {
        $this->setActivateToken(null);
        $this->setActivateTokenExpire(null);
    }

    /**
     * Generate new password request token and set token expire
     *
     * @return string Password request token
     */
    public function generatePasswordRequestToken()
    {
        $passwordToken = hash('sha256', mt_rand() . '#' . microtime());
        $this->setPasswordToken($passwordToken);
        $passwordTokenExpire = new \DateTime();
        $passwordTokenExpire->add(new \DateInterval('PT1H'));
        $this->setPasswordTokenExpire($passwordTokenExpire);

        return $passwordToken;
    }

    /**
     * Delete password request token and token expire
     */
    public function clearPasswordRequestToken()
    {
        $this->setPasswordToken(null);
        $this->setPasswordTokenExpire(null);
    }

    /**
     * Get PasswordToken
     *
     * @return string
     */
    public function getPasswordToken()
    {
        return $this->PasswordToken;
    }

    /**
     * Set PasswordToken
     *
     * @param string $passwordToken
     * @return User
     */
    public function setPasswordToken($passwordToken)
    {
        $this->PasswordToken = $passwordToken;
        $this->_setChanged('PasswordToken');

        return $this;
    }

    /**
     * Get PasswordTokenExpire
     *
     * @return \DateTime
     */
    public function getPasswordTokenExpire()
    {
        return $this->PasswordTokenExpire;
    }

    /**
     * Set PasswordTokenExpire
     *
     * @param \DateTime $passwordTokenExpire
     * @return User
     */
    public function setPasswordTokenExpire($passwordTokenExpire)
    {
        $this->PasswordTokenExpire = $passwordTokenExpire;
        $this->_setChanged('PasswordTokenExpire');

        return $this;
    }

    /**
     * Returns true, if password token has expired
     *
     * @return bool
     */
    public function hasPasswordTokenExpired()
    {
        return null === $this->PasswordTokenExpire || new \DateTime() > $this->PasswordTokenExpire;
    }

    /**
     * Add UserApplications
     *
     * @param UserApplication $userApplication
     * @return User
     */
    public function addUserApplication(UserApplication $userApplication)
    {
        $this->UserApplications[] = $userApplication;

        return $this;
    }

    /**
     * Remove UserApplications
     *
     * @param UserApplication $userApplication
     */
    public function removeUserApplication(UserApplication $userApplication)
    {
        $this->UserApplications->removeElement($userApplication);
    }

    /**
     * Get UserApplications
     *
     * @return UserApplication[]
     */
    public function getUserApplications()
    {
        return $this->UserApplications;
    }

    /**
     * Get ActivateToken
     *
     * @return string
     */
    public function getActivateToken()
    {
        return $this->ActivateToken;
    }

    /**
     * Set ActivateToken
     *
     * @param string $activateToken
     * @return User
     */
    public function setActivateToken($activateToken)
    {
        $this->ActivateToken = $activateToken;
        $this->_setChanged('ActivateToken');

        return $this;
    }

    /**
     * Get ActivateTokenExpire
     *
     * @return \DateTime
     */
    public function getActivateTokenExpire()
    {
        return $this->ActivateTokenExpire;
    }

    /**
     * Set ActivateTokenExpire
     *
     * @param \DateTime $activateTokenExpire
     * @return User
     */
    public function setActivateTokenExpire($activateTokenExpire)
    {
        $this->ActivateTokenExpire = $activateTokenExpire;
        $this->_setChanged('ActivateTokenExpire');

        return $this;
    }

    /**
     * Returns true, if password token has expired
     *
     * @return bool
     */
    public function hasActivateTokenExpired()
    {
        return null === $this->ActivateTokenExpire || new \DateTime() > $this->ActivateTokenExpire;
    }

    /**
     * Generate new activate request token and set token expire
     *
     * @return string Password request token
     */
    public function generateActivateRequestToken()
    {
        $activateToken = hash('sha256', mt_rand() . '#' . microtime());
        $this->setActivateToken($activateToken);
        $activateTokenExpire = new \DateTime();
        $activateTokenExpire->add(new \DateInterval('P1D'));
        $this->setActivateTokenExpire($activateTokenExpire);

        return $activateToken;
    }

    /**
     * @return string
     */
    public function getResetPasswordToken()
    {
        return $this->resetPasswordToken;
    }

    /**
     * @param string $resetPasswordToken
     */
    public function setResetPasswordToken($resetPasswordToken)
    {
        $this->resetPasswordToken = $resetPasswordToken;
        $this->_setChanged('resetPasswordToken');
    }

    /**
     * @return \DateTime
     */
    public function getResetPasswordTokenExpire()
    {
        return $this->resetPasswordTokenExpire;
    }

    /**
     * @param \DateTime $resetPasswordTokenExpire
     */
    public function setResetPasswordTokenExpire($resetPasswordTokenExpire)
    {
        $this->resetPasswordTokenExpire = $resetPasswordTokenExpire;
        $this->_setChanged('resetPasswordTokenExpire');
    }

    /**
     * @return string
     * @throws \Exception
     */
    public function generateResetPasswordToken()
    {
        $resetToken = hash('sha256', mt_rand() . '#' . microtime());
        $this->setResetPasswordToken($resetToken);
        $resetTokenExpire = new \DateTime();
        $resetTokenExpire->add(new \DateInterval('P1D'));
        $this->setResetPasswordTokenExpire($resetTokenExpire);

        return $resetToken;
    }

    /**
     * Get AuthId for identity-provider
     *
     * @return string
     */
    public function getAuthId()
    {
        return $this->AuthId;
    }

    /**
     * Set AuthId for identity-provider
     *
     * @param string $authId
     * @return User
     */
    public function setAuthId($authId)
    {
        $this->AuthId = $authId;
        $this->_setChanged('AuthId');

        return $this;
    }

    /**
     * Get LdapSearchAttributes
     *
     * @return string
     */
    public function getLdapSearchAttributes()
    {
        return $this->LdapSearchAttributes;
    }

    /**
     * Set LdapSearchAttributes
     *
     * @param string $ldapSearchAttributes
     * @return User
     */
    public function setLdapSearchAttributes($ldapSearchAttributes)
    {
        $this->LdapSearchAttributes = $ldapSearchAttributes;
        $this->_setChanged('LdapSearchAttributes');

        return $this;
    }

    /**
     * Get LdapSearchValue
     *
     * @return string
     */
    public function getLdapSearchValue()
    {
        return $this->LdapSearchValue;
    }

    /**
     * Set LdapSearchValue
     *
     * @param string $ldapSearchValue
     * @return User
     */
    public function setLdapSearchValue($ldapSearchValue)
    {
        $this->LdapSearchValue = $ldapSearchValue;
        $this->_setChanged('LdapSearchValue');

        return $this;
    }

    /**
     * Merge another model into this model
     *
     * @param Base|User $model
     */
    public function merge(Base $model)
    {
        foreach ($model->getChangedFields() as $field) {

            $methodName = ucfirst($field);

            // Password is already encrypted
            if ('Password' === $methodName) {
                $this->Password = $model->getPassword();
                $this->passwordPlain = $model->getPlainPassword();

                // Each time we receive a plain password
                // it is a new password submission
                // and we have to reset the expired flag
                if (0 < strlen($this->passwordPlain)) {
                    $this->passwordExpired = false;
                    $this->lastPasswordChange = new \DateTime();
                }

                continue;
            }

            $getter = 'get' . $methodName;
            if (!method_exists($model, $getter)) {
                $getter = 'is' . $methodName;
                if (!method_exists($model, $getter)) {
                    continue;
                }
            }
            $setter = 'set' . $methodName;
            if (!method_exists($this, $setter)) {
                continue;
            }

            // Set value by using the set-method
            $this->$setter($model->$getter());
        }
    }

    /**
     * Get deleted
     * @return boolean
     */
    public function getDeleted()
    {
        return (boolean)$this->Deleted;
    }

    /**
     * Get deleted
     * @return boolean
     */
    public function isDeleted()
    {
        return (boolean)$this->Deleted;
    }

    /**
     * Set deleted
     *
     * @param boolean $deleted
     *
     * @return User
     */
    public function setDeleted($deleted)
    {
        if (!$deleted) {
            $this->DeletedAt = null;
        } elseif (!$this->Deleted) { // Update deleted-at-date only if previous state was different
            $this->DeletedAt = new \DateTime();
        }
        $this->Deleted = (bool)$deleted;
        $this->_setChanged('Deleted');

        return $this;
    }

    /**
     * Get deletedAt
     *
     * @return \DateTime
     */
    public function getDeletedAt()
    {
        return $this->DeletedAt;
    }

    /**
     * Get createdAt
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->CreatedAt;
    }

    /**
     * Get updatedAt
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->UpdatedAt;
    }

    /**
     * Get updatedReferencesAt
     *
     * @return \DateTime
     */
    public function getUpdatedReferencesAt()
    {
        return $this->UpdatedReferencesAt;
    }

    /**
     * Set updatedReferencesAt
     *
     * @param \DateTime $updatedReferencesAt
     *
     * @return User
     */
    public function setUpdatedReferencesAt(\DateTime $updatedReferencesAt)
    {
        $this->UpdatedReferencesAt = $updatedReferencesAt;
        $this->_setChanged('UpdatedReferencesAt');

        return $this;
    }

    /**
     * If some user specific nested values
     * has been changed, user update date reference can be forced
     */
    public function setUserUpdatedReferences()
    {
        $this->UpdatedAt = new \DateTime();
        $this->UpdatedReferencesAt = new \DateTime();
        $this->_setChanged('UpdatedAt');
        $this->_setChanged('UpdatedReferencesAt');
    }

    /**
     * Get lastLoginAt
     *
     * @return \DateTime
     */
    public function getLastLoginAt()
    {
        return $this->LastLoginAt;
    }

    /**
     * User has logged in successfully
     */
    public function setLoginSuccess()
    {
        $this->loginRequest = true;
        $this->LastLoginAt = new \DateTime();
        $this->LoginFails = 0;
        $this->loginFailedFirstAt = null;
        $this->loginFailedLastAt = null;
        if ($this->MfaEnabled) {
            $this->MfaSecretReceived = true;
        }
    }

    /**
     * User has failed login
     */
    public function setLoginFailed()
    {
        $this->loginRequest = true;
        ++$this->LoginFails;
        //also add the dates
        if (null === $this->loginFailedFirstAt) {
            $this->setLoginFailedFirstAt(new \DateTime());
        }
        //always set the last date
        $this->setLoginFailedLastAt(new \DateTime());
    }

    /**
     * lifecycleCallbacks PreUpdate
     */
    public function onPreUpdate()
    {
        if (null === $this->CreatedAt) {
            $this->CreatedAt = new \DateTime();
        }
        if (null === $this->UpdatedAt || !$this->loginRequest) {
            $this->UpdatedAt = new \DateTime();
        }
    }

    /**
     * @return int
     */
    public function getLoginFails()
    {
        return $this->LoginFails;
    }

    /**
     * @param int $LoginFails
     */
    public function setLoginFails($LoginFails)
    {
        $this->LoginFails = $LoginFails;
        $this->_setChanged('LoginFails');
    }

    /**
     * @return User
     */
    public function resetLoginFails()
    {
        $this->LoginFails = 0;
        //reset login fails dates
        $this->loginFailedFirstAt = null;
        $this->loginFailedLastAt = null;

        return $this;
    }

    /**
     * @return boolean
     */
    public function isMfaEnabled()
    {
        return $this->MfaEnabled;
    }

    /**
     * @param boolean $MfaEnabled
     * @return User
     */
    public function setMfaEnabled($MfaEnabled)
    {
        $this->MfaEnabled = (boolean)$MfaEnabled;
        $this->_setChanged('MfaEnabled');
        if (!$this->MfaEnabled) {
            $this->setMfaSecret(null);
        }
        return $this;
    }

    /**
     * @return boolean
     */
    public function isMfaRecreate()
    {
        return $this->MfaRecreate;
    }

    /**
     * @param boolean $MfaRecreate
     * @return User
     */
    public function setMfaRecreate($MfaRecreate)
    {
        $this->MfaRecreate = (boolean)$MfaRecreate;
        $this->_setChanged('MfaRecreate');
        return $this;
    }

    /**
     * @return string
     */
    public function getMfaSecret()
    {
        return $this->MfaSecret;
    }

    /**
     * @param string $MfaSecret
     * @return User
     */
    public function setMfaSecret($MfaSecret)
    {
        if (null === $MfaSecret || !strlen($MfaSecret)) {
            $this->MfaCreatedAt = null;
            $this->MfaUpdatedAt = null;
            $this->MfaSecretReceived = true;
            $this->MfaSecret = null;
        } elseif ($this->MfaSecret !== $MfaSecret) {
            if (null === $this->MfaCreatedAt) {
                $this->MfaCreatedAt = new \DateTime();
            }
            $this->MfaUpdatedAt = new \DateTime();
            $this->MfaSecretReceived = false;
            $this->MfaSecret = $MfaSecret;
        }

        return $this;
    }

    /**
     * @return boolean
     */
    public function hasReceivedMfaSecret()
    {
        return $this->MfaSecretReceived;
    }

    /**
     * @return \DateTime|null
     */
    public function getMfaCreatedAt()
    {
        return $this->MfaCreatedAt;
    }

    /**
     * @return \DateTime|null
     */
    public function getMfaUpdatedAt()
    {
        return $this->MfaUpdatedAt;
    }

    /**
     * @return bool
     */
    public function isActivateTokenRecreate()
    {
        return $this->ActivateTokenRecreate;
    }

    /**
     * @param bool $ActivateTokenRecreate
     */
    public function setActivateTokenRecreate($ActivateTokenRecreate)
    {
        $this->ActivateTokenRecreate = $ActivateTokenRecreate;
        $this->_setChanged('ActivateTokenRecreate');
    }

    /**
     * @return \DateTime|null
     */
    public function getLoginFailedFirstAt()
    {
        return $this->loginFailedFirstAt;
    }

    /**
     * @param \DateTime|null $loginFailedFirstAt
     * @return User
     */
    public function setLoginFailedFirstAt($loginFailedFirstAt)
    {
        $this->loginFailedFirstAt = $loginFailedFirstAt;
        $this->_setChanged('loginFailedFirstAt');
        return $this;
    }

    /**
     * @return \DateTime|null
     */
    public function getLoginFailedLastAt()
    {
        return $this->loginFailedLastAt;
    }

    /**
     * @param \DateTime|null $loginFailedLastAt
     * @return User
     */
    public function setLoginFailedLastAt($loginFailedLastAt)
    {
        $this->loginFailedLastAt = $loginFailedLastAt;
        $this->_setChanged('loginFailedLastAt');
        return $this;
    }

    /**
     * @return null|string
     */
    public function getPasswordPolicy()
    {
        return $this->passwordPolicy;
    }

    /**
     * @param null|string $passwordPolicy
     * @return User
     */
    public function setPasswordPolicy($passwordPolicy)
    {
        $this->passwordPolicy = $passwordPolicy;
        $this->_setChanged('passwordPolicy');
        return $this;
    }

    /**
     * @return \DateTime|null
     */
    public function getLastPasswordChange()
    {
        return $this->lastPasswordChange;
    }

    /**
     * @param \DateTime|null $lastPasswordChange
     */
    public function setLastPasswordChange($lastPasswordChange)
    {
        if (!$this->_hasChanged('lastPasswordChange')) {
            $this->previousLastPasswordChange = $lastPasswordChange === null ? null : $this->lastPasswordChange;
        }
        $this->lastPasswordChange = $lastPasswordChange instanceof \DateTime ? clone $lastPasswordChange : null;
        $this->_setChanged('lastPasswordChange');
    }

    /**
     * @return bool
     */
    public function isPasswordExpired()
    {
        return $this->passwordExpired;
    }

    /**
     * @param bool $passwordExpired
     * @return User
     */
    public function setPasswordExpired($passwordExpired)
    {
        $this->passwordExpired = $passwordExpired;
        $this->_setChanged('passwordExpired');
        return $this;
    }

    /**
     * @return \DateTime|null
     */
    public function getPreviousLastPasswordChange()
    {
        return $this->previousLastPasswordChange;
    }

    /**
     * @param \DateTime|null $previousLastPasswordChange
     * @return User
     */
    public function setPreviousLastPasswordChange($previousLastPasswordChange)
    {
        $this->previousLastPasswordChange = $previousLastPasswordChange;
        return $this;
    }

    /**
     * @return string|null
     */
    public function getPasswordEncryptType(): ?string
    {
        return $this->passwordEncryptType;
    }

    /**
     * @param string $passwordEncryptType
     */
    public function setPasswordEncryptType(?string $passwordEncryptType): void
    {
        if (null !== $passwordEncryptType) {
            $this->passwordEncryptType = $passwordEncryptType;
            $this->_setChanged('passwordEncryptType');
        }
    }

    /**
     * This is needed for backwards compatibility for user-add
     * @param string $passwordEncryptType
     */
    public function setDefaultPasswordEncryptType(string $passwordEncryptType): void
    {
        if (null === $this->passwordEncryptType) {
            $this->passwordEncryptType = $passwordEncryptType;
        }
    }

    /**
     * @return ArrayCollection|UserGroup[]
     */
    public function getUserGroups()
    {
        return $this->userGroups;
    }

    /**
     * @return string[]
     */
    public function getUserGroupNames(): array
    {
        if (null === $this->userGroups) {
            return [];
        }

        $userGroupNames = [];
        foreach ($this->getUserGroups() as $userGroup) {
            $userGroupNames[] = $userGroup->getName();
        }

        return $userGroupNames;
    }

    /**
     * @param UserGroup $userGroup
     */
    public function addUserGroup(UserGroup $userGroup): void
    {
        if (!$this->userGroups->contains($userGroup)) {
            $this->userGroups->add($userGroup);
        }
    }

    /**
     * @param UserGroup $userGroup
     */
    public function removeUserGroup(UserGroup $userGroup): void
    {
        if ($this->userGroups->contains($userGroup)) {
            $this->userGroups->removeElement($userGroup);
        }
    }

    /**
     * @param ExecutionContextInterface $context
     */
    public function validatePasswordEncryptType(ExecutionContextInterface $context)
    {
        if ($this->_hasChanged('passwordEncryptType') && null === $this->passwordPlain) {
            $context->buildViolation('pe1##Password missing##Password encrypt type cannot be set without new password!')
                ->atPath('passwordEncryptType')
                ->addViolation();
        }
    }
}
