<?php
/**
 * @author      Aurélien BERNARD - TRYDEA - 2020
 */

declare(strict_types=1);

namespace Shared\Authentication\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Shared\Authentication\CustomFilters\OrSearchFilter;
use Shared\Authentication\Dto\UserPatchInput;
use Shared\Authentication\Dto\UserPostInput;
use Shared\TransferSync\Entity\StatusDataEntry;
use Symfony\Component\Ldap\Entry;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Webapp\Core\Entity\UserGroup;

/**
 * Class User.
 *
 * @ApiResource(
 *     collectionOperations={
 *         "get"={"security"="is_granted('USER_GET_COLLECTION', request)"},
 *         "post"={
 *              "security_post_denormalize"="is_granted('USER_POST', object)",
 *              "input"=UserPostInput::class,
 *          },
 *     },
 *     itemOperations={
 *         "get"={"security"="is_granted('USER_GET', object)"},
 *         "put"={"security"="is_granted('USER_EDIT', object)"},
 *         "patch"={
 *              "security"="is_granted('USER_EDIT', object)",
 *              "input"=UserPatchInput::class,
 *         },
 *         "delete"={"security"="is_granted('USER_DELETE', object)"},
 *     },
 *     normalizationContext={ "ignored_attributes" = {"projects", "password", "salt", "entry"} },
 *     denormalizationContext={ "ignored_attributes" = {"projects", "salt", "entry"} }
 * )
 * @ApiFilter(OrderFilter::class, properties={"username", "name", "surname"}, arguments={"orderParameterName"="order"})
 * @ApiFilter(SearchFilter::class, properties={
 *     "username": "ipartial",
 *     "id": "exact",
 *     "active": "exact",
 *     "groups": "exact",
 *     "siteRoles.site": "exact"})
 * @ApiFilter(OrSearchFilter::class, properties={"search_names"={
 *     "username": "utipartial",
 *     "name": "utipartial",
 *     "surname": "utipartial",
 *     }})
 * @ApiFilter(BooleanFilter::class, properties={"active"})
 * @ApiFilter(ExistsFilter::class, properties={"siteRoles"})
 *
 * @ORM\Entity(repositoryClass="Shared\Authentication\Repository\UserRepository")
 * @ORM\Table(name="ado_user", schema="shared")
 */
class User extends IdentifiedEntity implements UserInterface, PasswordAuthenticatedUserInterface
{

    /**
     * @var string
     * @ORM\Column(name="username", type="string", length=180, unique=true)
     * @Assert\NotBlank()
     * @Groups({"status_project_webapp_view", "status_project_webapp_return_data", "project_synthesis", "data_entry_synthesis", "variable_synthesis"})
     */
    private string $username;

    /**
     * @var string
     * @ORM\Column(name="name", type="string", unique=false)
     * @Groups({"status_project_webapp_view", "status_project_webapp_return_data", "protocol_synthesis", "project_synthesis", "data_entry_synthesis", "variable_synthesis"})
     */
    private string $name;

    /**
     * @var string
     * @ORM\Column(name="surname", type="string", unique=false)
     * @Groups({"status_project_webapp_view", "status_project_webapp_return_data", "platform_synthesis", "protocol_synthesis", "project_synthesis", "data_entry_synthesis", "variable_synthesis"})
     */
    private string $surname;

    /**
     * @var string
     * @ORM\Column(name="email", type="string", unique=false)
     * @Assert\NotBlank()
     * @Assert\Email()
     */
    private string $email;

    /**
     * @var bool
     * @ORM\Column(name="active", type="boolean")
     */
    private bool $active;

    /**
     * @var string[]
     * @ORM\Column(name="roles", type="json")
     */
    private array $roles;

    /**
     * @var string The hashed password
     * @ORM\Column(name="password", type="string")
     */
    private string $password = '';

    /**
     * @var Collection<array-key, RelUserSite>
     * @ORM\OneToMany(targetEntity="Shared\Authentication\Entity\RelUserSite", mappedBy="user", cascade={"persist"}, orphanRemoval=true)
     * @ORM\JoinTable(name="rel_user_site", schema="adonis")
     */
    private $siteRoles;

    /**
     * @var Collection<array-key, StatusDataEntry>|StatusDataEntry[]
     * @ORM\OneToMany(targetEntity="Shared\TransferSync\Entity\StatusDataEntry", mappedBy="user", cascade={"persist"})
     */
    private $projects;

    /**
     * @var Entry|null
     */
    private $entry;

    /**
     * @var Collection<array-key, UserGroup>|UserGroup[]
     * @ORM\ManyToMany(targetEntity="Webapp\Core\Entity\UserGroup", mappedBy="users")
     */
    private $groups;

    /**
     * @var string | null
     * @ORM\Column(name="avatar", type="text", nullable=true)
     */
    private $avatar;

    /**
     * User constructor.
     */
    public function __construct()
    {
        $this->roles = [];
        $this->siteRoles = new ArrayCollection();
        $this->projects = new ArrayCollection();
        $this->groups = new ArrayCollection();
        $this->active = true;
        $this->avatar = null;
    }

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

    /**
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return $this->username;
    }

    /**
     * @param string $username
     * @return $this
     */
    public function setUsername(string $username): self
    {
        $this->username = $username;

        return $this;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @param string $name
     * @return User
     */
    public function setName(string $name): User
    {
        $this->name = $name;
        return $this;
    }

    /**
     * @return string
     */
    public function getSurname(): string
    {
        return $this->surname;
    }

    /**
     * @param string $surname
     * @return User
     */
    public function setSurname(string $surname): User
    {
        $this->surname = $surname;
        return $this;
    }

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

    /**
     * @param string $email
     * @return User
     */
    public function setEmail(string $email): User
    {
        $this->email = $email;
        return $this;
    }

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

    /**
     * @param bool $active
     * @return User
     */
    public function setActive(bool $active): User
    {
        $this->active = $active;
        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // Guarantee every user at least has ROLE_USER.
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    /**
     * @param array $roles
     * @return $this
     */
    public function setRoles(array $roles): self
    {
        $this->roles = $roles;
        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return $this->password;
    }

    /**
     * @param string $password
     * @return $this
     */
    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @return Collection<array-key, RelUserSite>
     */
    public function getSiteRoles(): Collection
    {
        return $this->siteRoles;
    }

    /**
     * @param RelUserSite $site
     */
    public function addSite(RelUserSite $site): void
    {
        if (!$this->siteRoles->contains($site)) {
            $site->getSite()->addUserRole($site);
            $site->setUser($this);
            $this->siteRoles->add($site);
        }
    }

    /**
     * @param RelUserSite $site
     */
    public function removeSite(RelUserSite $site): void
    {
        if ($this->siteRoles->contains($site)) {
            $site->getSite()->removeUserRole($site);
            $this->siteRoles->removeElement($site);
        }
    }

    /**
     * @see UserInterface
     */
    public function getSalt(): ?string
    {
        return null; // not needed when using the "bcrypt" algorithm in security.yaml
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    /**
     * @return Collection
     */
    public function getProjects(): Collection
    {
        return $this->projects;
    }

    /**
     * @param StatusDataEntry $project
     */
    public function addProject(StatusDataEntry $project): void
    {
        if (!$this->projects->contains($project)) {
            $project->setUser($this);
            $this->projects->add($project);
        }
    }

    /**
     * @param StatusDataEntry $project
     */
    public function removeProject(StatusDataEntry $project): void
    {
        if ($this->projects->contains($project)) {
            $this->projects->removeElement($project);
        }
    }

    /**
     * @return ?Entry
     */
    public function getEntry(): ?Entry
    {
        return $this->entry;
    }

    /**
     * @param ?Entry $entry
     * @return User
     */
    public function setEntry(?Entry $entry): User
    {
        $this->entry = $entry;
        return $this;
    }

    /**
     * @return Collection
     */
    public function getGroups(): Collection
    {
        return $this->groups;
    }

    /**
     * @param Collection $groups
     * @return User
     */
    public function setGroups(Collection $groups): User
    {
        $this->groups = $groups;
        return $this;
    }

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

    /**
     * @param string|null $avatar
     * @return User
     */
    public function setAvatar(?string $avatar): User
    {
        $this->avatar = $avatar;
        return $this;
    }
}
