<?php

namespace AuthStack\Driver\System;

use AuthStack\Driver\Model\SecurityKey;
use AuthStack\Driver\Model\User as UserModel;
use AuthStack\Driver\Collection\TwoFactorKeys;
use AuthStack\Driver\Errors\NoUserObjectError;
use AuthStack\Driver\Driver\AuthDriverInterface;

class Driver extends AbstractSystemDriver implements AuthDriverInterface
{

    /**
     * @return array
     */
    public static function install()
    {
        return [
            'title'         => 'System driver',
            'description'   => 'System driver is used for administrative authentication against AuthStack MySQL database. It is not meant to be used in SSO process.',
            'class_path'    => 'AuthStack\\Driver\\System\\Driver'
        ];
    }


    /**
     * @param array $credentials
     *
     * @return bool
     * @throws \AuthStack\Driver\Errors\InsufficientCredentialsError
     */
    public function authenticate(array $credentials)
    {
        $this->check($credentials);

        $username = $credentials['username'];
        $password = $credentials['password'];

        $unique_id = sprintf("%d.%s", $this->getAuthSourceId(), $username);

        $user = UserModel::where('unique_id', sha1($unique_id, true))->firstOrFail();

        $result = password_verify($password, $user->password);

        if($result)
        {
            // Provision to AuthStack's database so we can associate security keys
            // with people from various authsources
            $this->provision($username);

            // Create and save user object
            $this->user = User::fromModel($user, $this->cfg_key);

            // Associate the user object with the model that handles provisioned users in AuthStack's database
            $this->getUser()->setProvisionedUser($this->provisioned);

            // AuthStack Id comes from ProvisionedUser model, so we'll set it now
            $this->getUser()->setAuthstackID($this->provisioned->id);

            // Set the authentication source for this user
            $this->getUser()->setAuthsourceID($this->getAuthSourceId());

            // Retrieve a list of security (two factor) keys associated with this user's account
            $keys = TwoFactorKeys::fromArray(
                SecurityKey::where("user_id", $this->getUser()->getProvisionedUser()->id)->get()->toArray()
            );

            // Set the key collection for the user
            $this->getUser()->setKeyCollection($keys);
        }

        return $result;
    }


    /**
     * @return User
     * @throws NoUserObjectError
     */
    public function getUser()
    {
        if(!$this->user instanceof User)
        {
            throw new NoUserObjectError('There is no user object available. You must successfully authenticate first.');
        }

        return $this->user;
    }


    /**
     * @param $username
     *
     * @return User
     * @throws NoUserObjectError
     * @throws \AuthStack\Driver\Errors\NoProvisionedUserError
     */
    public function getUserByUsername($username)
    {
        $unique_id = sprintf("%d.%s", $this->getAuthSourceId(), $username);

        $user = UserModel::where('unique_id', sha1($unique_id, true))->firstOrFail();

        // Provision to AuthStack's database so we can associate security keys
        // with people from various authsources
        $this->provision($username);

        // Create and save user object
        $this->user = User::fromModel($user, $this->cfg_key);

        // Associate the user object with the model that handles provisioned users in AuthStack's database
        $this->getUser()->setProvisionedUser($this->provisioned);

        // AuthStack Id comes from ProvisionedUser model, so we'll set it now
        $this->getUser()->setAuthstackID($this->provisioned->id);

        // Set the authentication source for this user
        $this->getUser()->setAuthsourceID($this->getAuthSourceId());

        // Retrieve a list of security (two factor) keys associated with this user's account
        $keys = TwoFactorKeys::fromArray(
            SecurityKey::where("user_id", $this->getUser()->getProvisionedUser()->id)->get()->toArray()
        );

        // Set the key collection for the user
        $this->getUser()->setKeyCollection($keys);

        return $this->getUser();
    }


    /**
     * @param $id
     *
     * @return User
     * @throws NoUserObjectError
     * @throws \AuthStack\Driver\Errors\NoProvisionedUserError
     */
    public function getUserByPrimaryKey($id)
    {
        $user = UserModel::findOrFail($id);

        // Provision to AuthStack's database so we can associate security keys
        // with people from various authsources
        $this->provision($user->username);

        // Create and save user object
        $this->user = User::fromModel($user, $this->cfg_key);

        // Associate the user object with the model that handles provisioned users in AuthStack's database
        $this->getUser()->setProvisionedUser($this->provisioned);

        // AuthStack Id comes from ProvisionedUser model, so we'll set it now
        $this->getUser()->setAuthstackID($this->provisioned->id);

        // Set the authentication source for this user
        $this->getUser()->setAuthsourceID($this->getAuthSourceId());

        // Retrieve a list of security (two factor) keys associated with this user's account
        $keys = TwoFactorKeys::fromArray(
            SecurityKey::where("user_id", $this->getUser()->getProvisionedUser()->id)->get()->toArray()
        );

        // Set the key collection for the user
        $this->getUser()->setKeyCollection($keys);

        return $this->getUser();
    }


    /**
     * @return bool
     */
    public function passwordChangeAllowed()
    {
        return true;
    }


    /**
     * @param array $data
     *
     * @return mixed
     * @throws NoUserObjectError
     */
    public function changePassword(array $data)
    {
        if(!isset($data['password']))
        {
            throw new \InvalidArgumentException("The array must contain 'password' key with new password.");
        }

        $model = UserModel::findOrFail($this->getUser()->getPrimaryKeyValue());

        $model->password = password_hash($data['password'], PASSWORD_BCRYPT);

        return $model->save();
    }
}