<?php

namespace AuthStack\UserDirectory\Model;

use function AuthStack\UserDirectory\routes;
use AuthStack\UserDirectory\Service\RESTService;
use Buckhill\Support\Traits\DotNotationAccessorTrait;
use AuthStack\UserDirectory\Exceptions\AccountException;

class Account extends AbstractModel
{
    use DotNotationAccessorTrait;

    protected $service;

    /**
     * @return int
     */
    public function getID(): int
    {
        return (int)$this->dotNotationFromModel('id');
    }

    /**
     * @return bool
     */
    public function isCreated(): bool
    {
        return !empty($this->getID());
    }

    /**
     * @param string $username
     * @param string $password
     * @param callable|null $afterError
     * @return Account
     */
    public function authenticate(string $username, string $password, callable $afterError = null): Account
    {
        return $this->__model($this->getService()->authenticate($username, $password, $afterError));
    }

    /**
     * @return string
     */
    public function getUsername(): string
    {
        return $this->dotNotationFromModel('username', '');
    }

    /**
     * @return string
     */
    public function getFirstName(): string
    {
        return $this->dotNotationFromModel('first_name', '');
    }

    /**
     * @return string
     */
    public function getLastName(): string
    {
        return $this->dotNotationFromModel('last_name', '');
    }

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

    /**
     * @return bool
     */
    public function isManager(): bool
    {
        return (bool)$this->model['is_manager'];
    }

    public function customAttribute($path, $default = null)
    {
        return $this->dotNotationFromModel(sprintf("additional.%s", $path), $default);
    }

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

        return $this;
    }

    /**
     * @param string $fname
     * @return Account
     */
    public function setFirstName(string $fname): self
    {
        $this->model['first_name'] = $fname;

        return $this;
    }

    /**
     * @param string $lname
     * @return Account
     */
    public function setLastName(string $lname): self
    {
        $this->model['last_name'] = $lname;

        return $this;
    }

    /**
     * @param string $email
     * @return Account
     */
    public function setEmail(string $email): self
    {
        $this->model['email'] = $email;

        return $this;
    }

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

        return $this;
    }

    /**
     * @param array $additional
     * @return $this
     */
    public function setAdditional(array $additional): self
    {
        $this->model['additional'] = $additional;

        return $this;
    }

    /**
     * @param int $id
     * @return $this
     */
    public function setPasswordPolicy(int $id): self
    {
        $this->model['policy_id'] = $id;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasOrganization(): bool
    {
        return !empty($this->model['organization_id']);
    }

    /**
     * @return mixed
     */
    public function getOrganizationID()
    {
        return $this->model['organization_id'];
    }

    /**
     * @return mixed
     */
    public function getOrganizationTitle()
    {
        return $this->model['org_title'];
    }

    /**
     * @return array
     */
    public function toArray(): array
    {
        return $this->model;
    }

    /**
     * @param array $model
     * @return array
     */
    public function createOrganizational(array $model): array
    {
        return $this->getService()->createOrganizational($model);
    }

    /**
     * @param Organization $organization
     * @return array
     */
    public function withOrganization(Organization $organization): array
    {
        $me = $this->getModel();
        $org = $organization->getModel();

        $merged = array_merge($me, $org);

        return $this->getService()->createOrganizational($merged);
    }

    /**
     * @param array $model
     * @return Account
     */
    public function create(array $model = []): Account
    {
        if(empty($model)) $model = $this->getModel();

        return $this->__model($this->getService()->create($model));
    }

    /**
     * @param int $id
     * @return Account
     */
    public function read(int $id): Account
    {
        return $this->__model($this->getService()->read($id));
    }

    /**
     * @param array $data
     * @return bool
     */
    public function update(array $data): bool
    {
        $result = $this->getService()->update($this->getID(), $data);

        $this->model = array_merge($this->model, $data);

        return $result;
    }

    /**
     * @param array $attributes
     * @return bool
     */
    public function updateCustomAttributes(array $attributes): bool
    {
        $result = $this->getService()->updateCustomAttributes($this->getID(), $attributes);

        $this->model['additional'] = array_merge($this->model['additional'], $attributes);

        return $result;
    }

    /**
     * @return bool
     */
    public function delete(): bool
    {
        return $this->getService()->delete($this->getID());
    }

    /**
     * @param int $group_id
     * @return bool
     */
    public function addToGroup(int $group_id): bool
    {
        return $this->getService()->addToGroup($this->getID(), $group_id);
    }

    /**
     * Array of numbers, like [1, 15, 27, 30] where numbers are numeric ID's of groups
     *
     * @param array $groups
     * @return bool
     */
    public function addToMultipleGroups(array $groups): bool
    {
        return $this->getService()->addToMultipleGroups($this->getID(), $groups);
    }

    /**
     * @param int $group_id
     * @return bool
     */
    public function removeFromGroup(int $group_id): bool
    {
        return $this->getService()->removeFromGroup($this->getID(), $group_id);
    }

    /**
     * @return bool
     */
    public function activate(): bool
    {
        return $this->getService()->activate($this->getID());
    }

    /**
     * @return bool
     */
    public function deactivate(): bool
    {
        return $this->getService()->deactivate($this->getID());
    }

    /**
     * @return bool
     */
    public function suspend(): bool
    {
        return $this->getService()->suspend($this->getID());
    }

    /**
     * @param array $options
     * @return array
     */
    public function listing(array $options = []): array
    {
        return $this->getService()->listing($options);
    }

    /**
     * @return bool
     */
    public function setManager(): bool
    {
        $this->model['is_manager'] = true;

        return $this->getService()->setManager($this->getID());
    }

    /**
     * @return bool
     */
    public function unsetManager(): bool
    {
        $this->model['is_manager'] = false;

        return $this->getService()->unsetManager($this->getID());
    }

    /**
     * @param \DateTime $when
     * @return bool
     */
    public function setExpiresAt(\DateTime $when): bool
    {
        $this->model['expires_at'] = $when->format('Y-m-d H:i:s');

        return $this->getService()->setExpiresAt($this->getID(), $when->format('Y-m-d H:i:s'));
    }

    /**
     * @return bool
     */
    public function setNeverExpires(): bool
    {
        $this->model['expires_at'] = null;

        return $this->getService()->setExpiresAt($this->getID(), null);
    }

    /**
     * @param $organization_id
     * @return bool
     */
    public function addToOrganization($organization_id): bool
    {
        return $this->getService()->addToOrganization($this->getID(), (int)$organization_id);
    }

    /**
     * @return bool
     */
    public function removeFromOrganization(): bool
    {
        return $this->getService()->removeFromOrganization($this->getID());
    }

    /**
     * @param string $username
     * @return bool
     */
    public function changeUsername(string $username): bool
    {
        return $this->getService()->changeUsername($this->getID(), $username);
    }

    /**
     * @param string $password
     * @return bool
     */
    public function changePassword(string $password): bool
    {
        return $this->getService()->changePassword($this->getID(), $password);
    }

    /**
     * @param int $policy_id
     * @return bool
     */
    public function changePasswordPolicy(int $policy_id): bool
    {
        return $this->getService()->changePasswordPolicy($this->getID(), (int)$policy_id);
    }

    /**
     * @param array $options
     * @return array
     */
    public function getLogs(array $options = []): array
    {
        return $this->getService()->getLogs($this->getID(), $options);
    }

    /**
     * @param \AuthStack\UserDirectory\Service\Account $service
     * @return $this
     */
    public function setService(RESTService $service)
    {
        $this->service = $service;

        return $this;
    }

    /**
     * @return \AuthStack\UserDirectory\Service\Account
     */
    public function getService(): RESTService
    {
        if(!$this->service instanceof \AuthStack\UserDirectory\Service\Account)
        {
            $baseUrl = $this->baseUrl;
            $authType = $this->auth_type;
            $token = $this->auth_token;

            $routes = routes($baseUrl, $authType, $token);

            $this->service = new \AuthStack\UserDirectory\Service\Account($baseUrl, $routes['options'], $routes['routes']['account']);

            if(!is_null($this->tz_offset) && !is_null($this->tz_dst))
            {
                $this->service->setTimeZoneOffset($this->tz_offset);
                $this->service->setTimeZoneDST($this->tz_dst);
            }
        }

        return $this->service;
    }

    /**
     * @param Account $model
     * @return Account
     */
    protected function __model(Account $model): Account
    {
        $this->model = $model->getModel();

        return $model->setService($this->getService());
    }
}