<?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 Shared\Utils\ArrayCollectionUtils;
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;

/**
 * @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
{
    /**
     * @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 = '';

    /**
     * @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 = '';

    /**
     * @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 = '';

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

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

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

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

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

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

    private ?Entry $entry = null;

    /**
     * @var Collection<int, UserGroup>
     *
     * @ORM\ManyToMany(targetEntity="Webapp\Core\Entity\UserGroup", mappedBy="users")
     */
    private Collection $groups;

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

    public function __construct()
    {
        $this->siteRoles = new ArrayCollection();
        $this->projects = new ArrayCollection();
        $this->groups = new ArrayCollection();
    }

    /**
     * @psalm-mutation-free
     */
    public function getUserIdentifier(): string
    {
        return $this->username;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

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

        return array_unique($roles);
    }

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

        return $this;
    }

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

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

        return $this;
    }

    /**
     * @return Collection<int,  RelUserSite>
     *
     * @psalm-mutation-free
     */
    public function getSiteRoles(): Collection
    {
        return $this->siteRoles;
    }

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

        return $this;
    }

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

        return $this;
    }

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

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

    /**
     * @return Collection<int,  StatusDataEntry>
     *
     * @psalm-mutation-free
     */
    public function getProjects(): Collection
    {
        return $this->projects;
    }

    /**
     * @param iterable<int, StatusDataEntry> $projects
     *
     * @return $this
     */
    public function setProjects(iterable $projects): self
    {
        ArrayCollectionUtils::update($this->projects, $projects, function (StatusDataEntry $statusDataEntry) {
            $statusDataEntry->setUser($this);
        });

        return $this;
    }

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

        return $this;
    }

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

        return $this;
    }

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

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

        return $this;
    }

    /**
     * @return Collection<int,  UserGroup>
     *
     * @psalm-mutation-free
     */
    public function getGroups(): Collection
    {
        return $this->groups;
    }

    /**
     * @param iterable<int,  UserGroup> $groups
     *
     * @return $this
     */
    public function setGroups(iterable $groups): self
    {
        ArrayCollectionUtils::update($this->groups, $groups);

        return $this;
    }

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

    /**
     * @return $this
     */
    public function setAvatar(?string $avatar): self
    {
        $this->avatar = $avatar;

        return $this;
    }
}
