<?php 

namespace NPS\Model;

use DateTime;
use NPS\Entity\UserEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Encoder\EncoderAwareInterface;
use Symfony\Component\Security\Core\Role\Role;
use NPS\Etc\Strings;
use NPS\Exception\WeakPasswordException;
use NPS\Entity\AccessGrantEntity;

/**
 * Represents a user model object
 * @author Lukas Korinek 
 */
class User implements IDomainObject, UserInterface/*, EncoderAwareInterface*/
{
    // Available roles array
    const ROLES = ['ROLE_ADMIN', 'ROLE_MANAGER', 'ROLE_ENGINEER', 'ROLE_TESTER', 'ROLE_USER'];

    /** @var UserEntity $entity */
    private $entity;

    /** @var UserPasswordEncoderInterface $encoder */
    private $encoder;

    /** @var array $roles */
    private $roles;

    public function __construct(UserEntity $entity, UserPasswordEncoderInterface $encoder)
    {
        $this->entity = $entity;
        $this->encoder = $encoder;
    }

    /**
     * @inheritdoc
     */
    public function getId()
    {
        return $this->entity->getId();
    }
    
    /**
     * @inheritdoc
     */
    public function getEntity()
    {
        return $this->entity;
    }

    /** 
     * Returns the date of user account registration
     * @return DateTime
     */
    public function getDateRegistered()
    {
        return $this->entity->dateRegistered;
    }

    /**
     * Gets the date of last credentials update
     * @return DateTime
     */
    public function getCredentialsUpdateDate()
    {
        return $this->entity->dateCredentialsUpdated;
    }

    /**
     * Returns the date of user account activation
     * @return DateTime|null
     */
    public function getDateActivated()
    {
        return $this->entity->dateActivated;
    }

    /**
     * Returns the date of last user login
     * @return DateTime|null
     */
    public function getLastLoginDate()
    {
        return $this->entity->lastLogin;
    }

    /**
     * Checks if the user is active
     * @return bool
     */
    public function isActive()
    {
        return (bool)$this->entity->dateActivated;
    }

    /**
     * @inheritdoc
     */
    public function getRoles()
    {
        if(!$this->roles)
        {
            $names = explode(',', $this->entity->roles);

            $this->roles = array_map(function($name)
            {
                return new Role($name);
            }, $names);
        }
        
        return $this->roles;
    }

    /**
     * @inheritdoc
     */
    public function getPassword()
    {
        return $this->entity->password;
    }

    /**
     * @inheritdoc
     */
    public function getSalt()
    {
        return $this->entity->salt;
    }

    /**
     * @inheritdoc
     */
    public function getUsername()
    {
        return $this->entity->username;
    }

    /**
     * Gets the users email
     * @return string
     */
    public function getEmail()
    {
        return $this->entity->email;
    }

    /**
     * Gets knowledge base bind id grants associated with this user
     * @return array 
     */
    public function getGrants()
    {
        if(!$this->entity->grants)
        {
            return [];
        }

        return array_values($this->entity->grants->map(function($grant){ return $grant->bindId; })->toArray());
    }


    //////////////////////////////////////////////////////////

    /**
     * Grants access to a knowledge base by bind id
     * @param int $bindId
     */
    public function grantAccess($bindId)
    {
        if($this->entity->grants->filter(function($grant) use ($bindId) { return $grant->bindId == $bindId;})->count())
        {
            return;
        }

        $grant = new AccessGrantEntity($this->entity, $bindId);
        $this->entity->grants->add($grant);
    }

    /**
     * Denies access to a knowledge base by bind id
     * @param int $bindId
     */
    public function denyAccess($bindId)
    {
        $found = $this->entity->grants->filter(function($grant) use ($bindId) { return $grant->bindId == $bindId;});
        if($grant = $found->first())
        {
            $grant->user = null;
            $this->entity->grants->removeElement($grant);
        }
    }


    /**
     * @inheritdoc
     */
    public function eraseCredentials()
    { 
        // Should not do anything
        // This function is called basically after every successful authentication
        // Its here to remove only non-persistent data
    }

    /**
     * Activates the user's account
     */
    public function activate()
    {
        if($this->isActive())
        {
            throw new \BadMethodCallException('The user is active already');
        }

        $this->entity->dateActivated = new \DateTime();
    }

    /**
     * Acknowledges that the user has been logged in
     */
    public function acknowledgeLogin()
    {
        $this->entity->lastLogin = new \DateTime();
    }

    /**
     * Validates and changes user's role assignments
     * @param array $roles
     */
    public function setRoles(array $roles)
    {
        // Validate
        foreach($roles as $role)
        {
            if(!in_array($role, self::ROLES))
            {
                throw new \InvalidArgumentException('Unknown role: ' . $roles);
            }
        }

        // Assign and invalidate
        $this->entity->roles = implode(',', $roles);
        $this->roles = null;

    }

    ///////////////////////////////////////////////////

    /**
     * Can be adjusted to dynamically select encoder by role
     * @inheritdoc
     */
    public function getEncoderName()
    {
        return 'sha512';
    }

    /**
     * Updates user's password
     * @param string $password New password
     */
    public function updatePassword($password)
    {
        if(!Strings::isStrongPassword($password))
        {
            throw new WeakPasswordException('Password must have at least 8 characters and include both letters and numbers');
        }
        
        $this->eraseCredentials(); // May be called anyway
        $this->entity->salt = Strings::random(20);
        $this->entity->password = $this->encoder->encodePassword($this, $password); 
        $this->entity->dateCredentialsUpdated = new \DateTime();
    }
}

