<?php

namespace AuthStack\UserDirectory\Service;

use AuthStack\UserDirectory\Exceptions\AccountException;

use AuthStack\UserDirectory\Exceptions\UserDirectoryException;
use AuthStack\UserDirectory\Model\Account as AccountModel;
use AuthStack\UserDirectory\Model\Organization;

class Account extends RESTService
{
    protected $account_id;

    public function setID($id)
    {
        $this->account_id = $id;

        return $this;
    }

    /**
     * @param string $username
     * @param string $password
     * @param callable $after
     * @return AccountModel
     * @throws AccountException
     */
    public function authenticate(string $username, string $password, callable $after = null): AccountModel
    {
        return $this->remote(function() use ($username, $password)
        {
            $data = $this->json('authenticate', ['username' => $username, 'password' => $password]);

            return new AccountModel($data);

        }, AccountException::class, $after);
    }

    /**
     * @param array $model
     * @param callable|null $after
     * @return array
     */
    public function createOrganizational(array $model, callable $after = null): array
    {
        return $this->remote(function() use (&$model)
        {
            $result = $this->json('create_organizational', $model);

            $model = array_merge($model, $result);

            $account = new AccountModel(array_merge($model, ['id' => $result['account_id']]));
            $org = new Organization(array_merge($model, ['id' => $result['organization_id']]));

            return ['account' => $account, 'organization' => $org];

        }, AccountException::class, $after);
    }

    /**
     * @param array $model
     * @param callable|null $after
     * @return AccountModel
     */
    public function create(array $model, callable $after = null): AccountModel
    {
        return $this->remote(function() use (&$model)
        {
            $data = $this->json('create', $model);

            return new AccountModel($data);

        }, AccountException::class, $after);
    }

    /**
     * @param int|null $id
     * @param callable|null $after
     * @return AccountModel
     */
    public function read(int $id = null, callable $after = null): AccountModel
    {
        return $this->remote(function() use ($id)
        {
            $data = $this->json('read', [], [$id]);

            return new AccountModel($data);

        }, AccountException::class, $after);
    }

    /**
     * @param int|null $id
     * @param array $data
     * @return bool
     */
    public function update(int $id = null, array $data, callable $after = null): bool
    {
        return $this->remote(function() use ($id, &$data)
        {
            $data = $this->json('update', $data, [$id]);

            return true;

        }, AccountException::class, $after);
    }

    /**
     * @param int|null $id
     * @param array $data
     * @return bool
     */
    public function updateCustomAttributes(int $id = null, array $data): bool
    {
        return $this->remote(function() use ($id, &$data)
        {
            $data = $this->json('update_custom_attr', ['attributes' => $data], [$id]);

            return true;

        }, AccountException::class);
    }

    /**
     * @param int|null $id
     * @return bool
     */
    public function delete(int $id = null): bool
    {
        return $this->remote(function() use ($id)
        {
            $data = $this->json('delete', [], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param int $group_id
     * @return bool
     */
    public function addToGroup(int $id, int $group_id): bool
    {
        return $this->remote(function() use ($id, $group_id)
        {
            $data = $this->json('add_to_group', [], [$id, $group_id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param array $groups
     * @return bool
     */
    public function addToMultipleGroups(int $id, array $groups): bool
    {
        return $this->remote(function() use ($id, $groups)
        {
            $data = $this->json('add_to_multiple_groups', ['groups' => $groups], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param int $group_id
     * @return bool
     */
    public function removeFromGroup(int $id, int $group_id): bool
    {
        return $this->remote(function() use ($id, $group_id)
        {
            $data = $this->json('remove_from_group', [], [$id, $group_id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @return bool
     */
    public function activate(int $id): bool
    {
        return $this->remote(function() use ($id)
        {
            $result = $this->json('activate', [], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @return bool
     */
    public function deactivate(int $id): bool
    {
        return $this->remote(function() use ($id)
        {
            $result = $this->json('deactivate', [], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @return bool
     */
    public function suspend(int $id): bool
    {
        return $this->remote(function() use ($id)
        {
            $result = $this->json('suspend', [], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param array $options
     * @return array
     */
    public function listing(array $options = []): array
    {
        return $this->remote(function() use (&$options)
        {
            $result = $this->json('listing', null, null, $options);

            return $result;

        }, AccountException::class);
    }

    /**
     * @param int $id
     * @return bool
     */
    public function setManager(int $id): bool
    {
        return $this->remote(function() use ($id)
        {
            $result = $this->json('setmanager', [], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @return bool
     */
    public function unsetManager(int $id): bool
    {
        return $this->remote(function() use ($id)
        {
            $result = $this->json('unsetmanager', [], [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param string|null $when
     * @return bool
     */
    public function setExpiresAt(int $id, string $when = null): bool
    {
        return $this->remote(function() use ($id, $when)
        {
            // If $when is null, then account never expires, else it expires at $when
            $params = is_null($when) ? ['never_expires' => true] : ['expires_at' => $when];

            $result = $this->json('expiry', $params, [$id]);

            return true;
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param int $organization_id
     * @return bool
     */
    public function addToOrganization(int $id, int $organization_id): bool
    {
        return $this->remote(function() use ($id, $organization_id)
        {
            $response = $this->json('add_to_org', [], [$id, $organization_id]);

            return true;

        }, AccountException::class);
    }

    /**
     * @param int $id
     * @return bool
     */
    public function removeFromOrganization(int $id): bool
    {
        return $this->remote(function() use ($id)
        {
            $response = $this->json('remove_from_org', [], [$id]);

            return true;

        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param string $username
     * @return bool
     */
    public function changeUsername(int $id, string $username): bool
    {
        return $this->remote(function() use ($id, $username)
        {
            $response = $this->json('change_username', ['username' => $username], [$id]);

            return $response['result'];
        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param string $password
     * @return bool
     */
    public function changePassword(int $id, string $password): bool
    {
        return $this->remote(function() use ($id, $password)
        {
            $response = $this->json('change_password', ['password' => $password], [$id]);

            return $response['result'];

        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param int $policy_id
     * @return bool
     */
    public function changePasswordPolicy(int $id, int $policy_id): bool
    {
        return $this->remote(function() use ($id, $policy_id)
        {
            $response = $this->json('change_ppolicy', [], [$id, $policy_id]);

            return $response['result'];

        }, AccountException::class);
    }

    /**
     * @param int $id
     * @param array $options
     * @return mixed
     */
    public function getLogs(int $id, array $options = [])
    {
        return $this->remote(function() use ($id, $options)
        {
            $response = $this->json('logs', [], [$id], $options);

            return $response;

        }, AccountException::class);
    }

    /**
     * @param int|null $id
     * @return int
     * @throws AccountException
     */
    protected function __id(int $id = null) : int
    {
        if(!is_int($this->account_id) && !is_int($id))
        {
            throw new AccountException("Incorrect method call - no Account ID specified");
        }

        return is_int($id) ? $id : $this->account_id;
    }
}