<?php

namespace App\Entity;


use App\Repository\UserTypeRepository;
use App\Utils\Utils;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @ORM\Table(name="user")
 * @ORM\HasLifecycleCallbacks
 */
class User implements UserInterface
{
    const MAX_WORKSHEETS_PER_USER = 3;

    // Costs of password encryption
    const CRYPT_COST = 12;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", length=191, nullable=false, unique=true)
     */
    protected $email;

    /**
     * @ORM\ManyToOne(targetEntity="UserType")
     * @ORM\JoinColumn(name="user_type", referencedColumnName="id")
     */
    protected $userType;

    /**
     * @ORM\ManyToOne(targetEntity="User")
     * @ORM\JoinColumn(name="creation_user", referencedColumnName="id")
     */
    protected $creationUser;

    /**
     * @ORM\Column(type="string", length=60, nullable=false)
     */
    protected $password;

    /**
     * @ORM\Column(type="string", length=100, nullable=false)
     */
    protected $firstname;

    /**
     * @ORM\Column(type="string", length=100, nullable=false)
     */
    protected $lastname;

    /**
     * @ORM\OneToMany(targetEntity="UserWorksheet", mappedBy="user", cascade={"persist", "remove"})
     */
    protected $worksheets;

    /**
     * @ORM\Column(type="smallint", nullable=false, options={"unsigned" = true})
     */
    protected $maxWorksheets;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $company;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $department;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $occupation;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $industry;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $organisationUnit;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $phoneNo;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $invoiceRecipient;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $invoiceDepartment;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $invoiceStreet;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $invoiceZip;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $invoiceCity;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $orderNo;

    /**
     * @ORM\Column(type="string", length=100, nullable=true)
     */
    protected $costCenter;

    /**
     * @ORM\Column(type="boolean", nullable=false)
     */
    protected $active;

    /**
     * @ORM\Column(type="datetime", nullable=false)
     */
    protected $creationDate;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    protected $validUntilDate;

    /**
     * @ORM\Column(type="boolean", options={"default":"0"})
     */
    protected $acceptedTerms;

    /**
     * @ORM\Column(type="boolean", options={"default":"0"})
     */
    protected $acceptedLegal;

    /**
     * User constructor.
     * @param ObjectManager $em
     * @param UserType $userType
     * @param $email
     * @param $firstName
     * @param $lastName
     * @param $password
     * @param User|null $editingUser
     * @throws \Exception
     */
    public function __construct(ObjectManager $em, UserType $userType, $email, $firstName, $lastName, $password, User $editingUser = null)
    {
        // Check email
        if (!Utils::validateEmail($em, $email)) {
            throw new Exception('invalid or existent email given: '.$email);
        }

        if ($userType->getType() === UserTypeRepository::USER_TYPE_SYSTEM) {
            $systemUser = $em->getRepository(self::class)->findBy(['userType' => $userType]);
            if (!empty($systemUser)) {
                throw new Exception('system user already exists');
            }
        }
        $this->setUserType($userType, $editingUser);

        // Check password length
        if (!Utils::isValidPasswordLength($password)) {
            throw new Exception('invalid password length');
        }

        $this->email = $email;
        $this->firstname = $firstName;
        $this->lastname = $lastName;
        $this->setPassword($password);
        $this->active = true;
        $this->creationUser = $editingUser;
        $this->maxWorksheets = self::MAX_WORKSHEETS_PER_USER;
        $this->creationDate = new \DateTime('now', new \DateTimeZone('Europe/Berlin'));
        $this->acceptedTerms = false;
        $this->acceptedLegal = false;

        if ($userType->getType() === UserTypeRepository::USER_TYPE_USER) {
            $this->validUntilDate = new \DateTime('now', new \DateTimeZone('Europe/Berlin'));
            $this->validUntilDate->modify('+3 year');
        }
    }

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

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

    /**
     * @return mixed
     */
    public function getUserType()
    {
        return $this->userType;
    }

    /**
     * @return mixed
     */
    public function getFirstname()
    {
        return $this->firstname;
    }

    /**
     * @return mixed
     */
    public function getLastname()
    {
        return $this->lastname;
    }

    /**
     * @return mixed
     */
    public function getWorksheets()
    {
        return $this->worksheets;
    }

    /**
     * @param mixed $worksheets
     */
    public function setWorksheets($worksheets): void
    {
        $this->worksheets = $worksheets;
    }

    /**
     * @return int
     */
    public function getMaxWorksheets(): int
    {
        return $this->maxWorksheets;
    }

    /**
     * @param int $maxWorksheets
     */
    public function setMaxWorksheets(int $maxWorksheets): void
    {
        $this->maxWorksheets = $maxWorksheets;
    }

    /**
     * @return User|null
     */
    public function getCreationUser(): ?User
    {
        return $this->creationUser;
    }

    /**
     * @return mixed
     */
    public function getCompany()
    {
        return $this->company;
    }

    /**
     * @param mixed $company
     */
    public function setCompany($company): void
    {
        $this->company = $company;
    }

    /**
     * @return mixed
     */
    public function getDepartment()
    {
        return $this->department;
    }

    /**
     * @param mixed $department
     */
    public function setDepartment($department): void
    {
        $this->department = $department;
    }

    /**
     * @return mixed
     */
    public function getOccupation()
    {
        return $this->occupation;
    }

    /**
     * @param mixed $occupation
     */
    public function setOccupation($occupation): void
    {
        $this->occupation = $occupation;
    }

    /**
     * @return mixed
     */
    public function getIndustry()
    {
        return $this->industry;
    }

    /**
     * @param mixed $industry
     */
    public function setIndustry($industry): void
    {
        $this->industry = $industry;
    }

    /**
     * @return mixed
     */
    public function getOrganisationUnit()
    {
        return $this->organisationUnit;
    }

    /**
     * @param mixed $organisationUnit
     */
    public function setOrganisationUnit($organisationUnit): void
    {
        $this->organisationUnit = $organisationUnit;
    }

    /**
     * @return mixed
     */
    public function getPhoneNo()
    {
        return $this->phoneNo;
    }

    /**
     * @param mixed $phoneNo
     */
    public function setPhoneNo($phoneNo): void
    {
        $this->phoneNo = $phoneNo;
    }

    /**
     * @return mixed
     */
    public function getInvoiceRecipient()
    {
        return $this->invoiceRecipient;
    }

    /**
     * @param mixed $invoiceRecipient
     */
    public function setInvoiceRecipient($invoiceRecipient): void
    {
        $this->invoiceRecipient = $invoiceRecipient;
    }

    /**
     * @return mixed
     */
    public function getInvoiceDepartment()
    {
        return $this->invoiceDepartment;
    }

    /**
     * @param mixed $invoiceDepartment
     */
    public function setInvoiceDepartment($invoiceDepartment): void
    {
        $this->invoiceDepartment = $invoiceDepartment;
    }

    /**
     * @return mixed
     */
    public function getInvoiceStreet()
    {
        return $this->invoiceStreet;
    }

    /**
     * @param mixed $invoiceStreet
     */
    public function setInvoiceStreet($invoiceStreet): void
    {
        $this->invoiceStreet = $invoiceStreet;
    }

    /**
     * @return mixed
     */
    public function getInvoiceZip()
    {
        return $this->invoiceZip;
    }

    /**
     * @param mixed $invoiceZip
     */
    public function setInvoiceZip($invoiceZip): void
    {
        $this->invoiceZip = $invoiceZip;
    }

    /**
     * @return mixed
     */
    public function getInvoiceCity()
    {
        return $this->invoiceCity;
    }

    /**
     * @param mixed $invoiceCity
     */
    public function setInvoiceCity($invoiceCity): void
    {
        $this->invoiceCity = $invoiceCity;
    }

    /**
     * @return mixed
     */
    public function getOrderNo()
    {
        return $this->orderNo;
    }

    /**
     * @param mixed $orderNo
     */
    public function setOrderNo($orderNo): void
    {
        $this->orderNo = $orderNo;
    }

    /**
     * @return mixed
     */
    public function getCostCenter()
    {
        return $this->costCenter;
    }

    /**
     * @param mixed $costCenter
     */
    public function setCostCenter($costCenter): void
    {
        $this->costCenter = $costCenter;
    }

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

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

    /**
     * @return mixed
     */
    public function getCreationDate()
    {
        return $this->creationDate;
    }

    /**
     * @return \DateTime
     */
    public function getValidUntilDate(): ?\DateTime
    {
        return $this->validUntilDate;
    }

    /**
     * Returns username as part of UserInterface needed in Symfony
     * @return string
     */
    public function getUsername()
    {
        return $this->email;
    }

    /**
     * Returns roles as part of UserInterface needed in Symfony
     * @return array
     */
    public function getRoles()
    {
        return UserTypeRepository::getUserRoles($this->userType);
    }

    /**
     * Erases credentials as part of UserInterface needed in Symfony
     */
    public function eraseCredentials() {}

    /**
     * Checks password as part of UserInterface needed in Symfony
     * @param $password
     * @return bool
     */
    public function checkPassword($password)
    {
        return $this->active && password_verify($password, $this->password);
    }

    /**
     * Returns salt as part of UserInterface needed in Symfony
     * @return null|string|void
     */
    public function getSalt() {}

    /**
     * @param UserType $userType
     * @param User|null $editingUser
     */
    public function setUserType(UserType $userType, User $editingUser = null): void
    {
        if ($userType->getType() !== UserTypeRepository::USER_TYPE_SYSTEM) {
            if ($editingUser === null) {
                throw new Exception('editing user required');
            }

            if (!UserTypeRepository::isHigherUserType($editingUser->getUserType(), $userType)) {
                throw new Exception('creating user has not enough rights to create this user type');
            }
        }
        $this->userType = $userType;
    }

    /**
     * Sets email with check on validity and existence
     * @param ObjectManager $em
     * @param $email
     */
    public function setEmail(ObjectManager $em, $email)
    {
        if ($email != $this->email) {
            if (!Utils::validateEmail($em, $email)) {
                throw new Exception('email is invalid or already exists: '.$email);
            }
            $this->email = $email;
        }
    }

    /**
     * @param bool|string $password
     */
    public function setPassword($password): void
    {
        $this->password = password_hash($password, PASSWORD_BCRYPT, ["cost" => self::CRYPT_COST]);
    }

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

    /**
     * @param \DateTime $validUntilDate
     */
    public function setValidUntilDate(?\DateTime $validUntilDate): void
    {
        $this->validUntilDate = $validUntilDate;
    }

    /**
     * @param mixed $lastname
     */
    public function setLastname($lastname): void
    {
        $this->lastname = $lastname;
    }

    /**
     * @param string $firstname
     * @return User
     */
    public function setFirstname(string $firstname): self
    {
        $this->firstname = $firstname;

        return $this;
    }

    /**
     * @param bool $visible
     */
    public function setVisible(bool $visible): void
    {
        $this->visible = $visible;
    }

    /**
     * @return mixed
     */
    public function getAcceptedTerms()
    {
        return $this->acceptedTerms;
    }

    /**
     * @param mixed $acceptedTerms
     */
    public function setAcceptedTerms($acceptedTerms): void
    {
        $this->acceptedTerms = $acceptedTerms;
    }

    /**
     * @return mixed
     */
    public function getAcceptedLegal()
    {
        return $this->acceptedLegal;
    }

    /**
     * @param mixed $acceptedLegal
     */
    public function setAcceptedLegal($acceptedLegal): void
    {
        $this->acceptedLegal = $acceptedLegal;
    }

    /**
     * Returns whether this user is on higher user role level than given user to compare with
     * @param User $compareUser
     * @return bool
     */
    public function isHigherUser(User $compareUser)
    {
        return UserTypeRepository::isHigherUserType($this->userType, $compareUser->getUserType());
    }


    /**
     * Sets client data
     * @param ObjectManager $em
     * @param $clientData
     */
    public function setClientData(ObjectManager $em, $clientData)
    {
        $this->setFirstname($clientData->firstname);
        $this->setLastname($clientData->lastname);
        $this->setEmail($em, $clientData->email);
    }

}
