<?php
class Authentication_model extends Cf_Model
{
    protected $userResource = 'user';
    protected $changePasswordResource = 'me/change-password';
    protected $rolesResource = 'role';
    protected $defaultRolesResource = 'default_role';
    public const CF_PASSWORD_RESET_HEADER = 'Cf-Password-Reset';

    public function __construct()
    {
        parent::__construct();
        $this->load->config('ion_auth', true);
        $this->load->helper('cookie');
        $this->load->helper('date');
    }
    /**
     * For login operation
     * @param type $username
     * @param type $password
     * @param type $remmember
     * @return type
     */
    public function login($identity, $password, $mode = 'oauth2', ?int $life_time = null, $twoFaCode = '')
    {
        $mpClient = config_item('MP_CLIENT_ID');
        $mpSecret = config_item('MP_CLIENT_SECRET');
        $grantType = config_item('MP_GRANT_TYPE');

        $data = [
            'username' => $identity,
            'password' => $password,
            'client_id' => $mpClient,
            'grant_type' => $grantType,
        ];

        if ($life_time !== null) {
            $data['life_time'] = $life_time;
        }

        if (!empty($mpSecret)) {
            $data['client_secret'] = $mpSecret;
        }

        try {
            $body = $this->getRestClient()->post('/oauth2/token', $data, [
                Cf_RestInstance::CF_2FA_TOKEN_HEADER => $twoFaCode,
            ]);
            $data = $this->checkData($body);
            if (is_array($data) && $this->hasErrors() == 0) {
                if (!empty($data['access_token'])) {
                    log_message('debug', 'Login successful with access token');
                    $this->getRestClient()->setupOauthAuth($data['access_token']);
                    return $data;
                } else {
                    throw new Exception('Invalid server response cannot access server token.');
                }
                delete_cookie('dark_mode');
            }
            throw new Exception($this->getErrorsString());
        } catch (HttpClient_Unauthorized $e) {
            log_message(
                'info',
                'login ' . get_class($e) . '  ' . $e->getMessage() . ' ' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'login ' . get_class($e) . '  ' . $e->getMessage() . ' ' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        }
    }

    public function revokeExternalUsersTokens()
    {
        try {
            return $this->getRestClient()->post('/oauth2/external-users-tokens/revoke');
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'Cannot revoke external users access token. Error:: ' . $e->getMessage(),
            );
            throw $e;
        }
    }

    public function unsetToken($token)
    {
        try {
            $tokens = is_array($token) ? $token : [$token];
            $data = [
                'tokens' => $tokens,
            ];
            $response = $this->getRestClient()->post('/oauth2/token/revoke', $data);
            return $response;
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), 'Cannot unset access token. Error:: ' . $e->getMessage());
            throw $e;
        }
    }

    public function getActiveSession()
    {
        try {
            $response = $this->getRestClient()->get('/oauth2/token/');
            return json_decode($response, true);
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'Cannot get active sessions from API. Error:: ' . $e->getMessage(),
            );
            throw $e;
        }
    }
    /**
     * Get Roles for a users
     */
    public function getRolesForUser($username)
    {
        try {
            $user = $this->getUserDetails($username);
            if (key_exists('roles', $user)) {
                return $user['roles'];
            }
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), 'getRolesForUser , Error grabbing roles for user');
            return false;
        }
    }
    /**
     * Get all roles
     */
    public function getAllRoles()
    {
        try {
            $body = $this->getRestClient()->get('/' . $this->rolesResource);
            $data = $this->checkData($body);
            if (is_array($data) && $this->hasErrors() == 0) {
                return $data['data'];
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'getAllRoles ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     * Get default role
     */
    public function getDefaultRole()
    {
        try {
            $body = $this->getRestClient()->get('/' . $this->defaultRolesResource);
            $data = $this->checkData($body);
            if ($this->hasErrors() == 0) {
                return $data;
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'getDefaultRole ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }

    public function setDefaultRole($name)
    {
        try {
            $body = $this->getRestClient()->post('/' . $this->defaultRolesResource, ['name' => $name]);
            $data = $this->checkData($body);
            if ($this->hasErrors() == 0) {
                return $data;
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'setDefaultRole ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     * Get the details of a role
     * @param type $rolename
     */
    public function getRoleDetails($rolename)
    {
        try {
            $body = $this->getRestClient()->get('/' . $this->rolesResource . '/' . $rolename);
            $data = $this->checkData($body);
            if (is_array($data) && $this->hasErrors() == 0) {
                return $data['data'][0];
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'getRoleDetails ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     * For creating users
     * @param type $data
     */
    public function createRole($data)
    {
        try {
            $this->getRestClient()->put('/' . $this->rolesResource . '/' . $data['name'], $data);
            if ($this->getRestClient()->lastStatus() == 201) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'createRole ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     *
     * @param type $username
     * @param type $data
     * @return type boolean
     */
    public function updateRole($rolename, $data)
    {
        try {
            $this->getRestClient()->post('/' . $this->rolesResource . '/' . $rolename, $data);
            if ($this->getRestClient()->lastStatus() == 204) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'updateRole ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     * Deletes a role
     * @param type $rolename
     */
    public function deleteRole($rolename)
    {
        try {
            $this->getRestClient()->delete('/' . $this->rolesResource . '/' . $rolename);
            $this->cache->clean();
            if ($this->getRestClient()->lastStatus() == 204) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'deleteRole ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     * Create a user
     * @param type $data
     * @return type
     */
    public function createUser($data)
    {
        try {
            $this->getRestClient()->put('/' . $this->userResource . '/' . $data['id'], $data);
            if ($this->getRestClient()->lastStatus() == 201) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'createUser ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     *
     * @return type array data
     */
    public function getAllUsers(
        $page = 1,
        $count = 50,
        $userType = null,
        $returnWithMeta = false,
        $determineInactive = false,
    ) {
        try {
            $params = [
                'page' => $page,
                'count' => $count,
                'determineInactiveUsers' => $determineInactive ? 'true' : 'false',
            ];

            if ($userType === 'external') {
                $params['external'] = 'true';
            } elseif ($userType === 'internal') {
                $params['external'] = 'false';
            }

            $queryString = http_build_query($params);
            $body = $this->getRestClient()->get("/{$this->userResource}?{$queryString}");

            $data = $this->checkData($body);

            if (is_array($data) && $this->hasErrors() == 0) {
                if ($returnWithMeta) {
                    return $data;
                } else {
                    return $data['data'];
                }
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'getAllUsers ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /*get a user list for searched users*/

    public function searchUser($userType, $usernameExpression, $returnWithMeta = false)
    {
        try {
            if ($userType == 'external') {
                $body = $this->getRestClient()->get(
                    '/' . $this->userResource . '?external=true&id=' . $usernameExpression,
                );
            } elseif ($userType == 'internal') {
                $body = $this->getRestClient()->get(
                    '/' . $this->userResource . '?external=false&id=' . $usernameExpression,
                );
            } else {
                $body = $this->getRestClient()->get('/' . $this->userResource . '?id=' . $usernameExpression);
            }

            $data = $this->checkData($body);
            if (is_array($data) && $this->hasErrors() == 0) {
                if ($returnWithMeta) {
                    return $data;
                } else {
                    return $data['data'];
                }
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'getAllUsers ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     * Generate the details for given object i.e user
     * @param type $username
     * @param type $password
     * @param type $object
     * @return type
     */
    public function getUserDetails($username)
    {
        try {
            $body = $this->getRestClient()->get('/' . $this->userResource . '/' . $username);

            $data = $this->checkData($body);
            if (is_array($data) && $this->hasErrors() == 0) {
                return $data['data'][0];
            } else {
                throw new Exception($this->getErrorsString());
            }
        } catch (HttpClient_NotFound $e) {
            log_message(
                'info',
                'getUserDetails  ' .
                    get_class($e) .
                    ' ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'getUserDetails  ' .
                    get_class($e) .
                    ' ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     *
     * @param type $id
     * @param type $data
     */
    public function update_user($id, $data, $headers = [])
    {
        try {
            $this->getRestClient()->post('/' . $this->userResource . '/' . $id, $data, $headers);
            $this->cache->clean();
            if ($this->getRestClient()->lastStatus() == 202) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            $this->setError($e->getMessage());
            log_message(
                log_level_for_exception($e),
                'update_user ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     * Delete the supplied user
     * @param type $username
     */
    public function deleteUser($id)
    {
        try {
            $this->getRestClient()->delete('/' . $this->userResource . '/' . $id);
            $this->cache->clean();
            if ($this->getRestClient()->lastStatus() == 202) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'deleteUser ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
    /**
     * Change user password
     *
     * @param type $id - user id
     * @param type $old - old passwd
     * @param type $new - new passwd
     * @return boolean
     * @throws Exception
     */

    public function change_password($id, $old, $new, $twoFaCode = '')
    {
        if (!$this->isPasswordValid($id, $old, $twoFaCode)) {
            return false;
        }

        try {
            $data = ['password' => $new, 'old_password' => $old];
            $headers = [Cf_RestInstance::CF_2FA_TOKEN_HEADER => $twoFaCode];
            $this->getRestClient()->post('/' . $this->changePasswordResource, $data, $headers);
            if ($this->getRestClient()->lastStatus() == 202) {
                return true;
            }
            return false;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'change_password ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     * Validates the provided password by making a request to the API using basic authentication.
     *
     * @param string $username The username to authenticate.
     * @param string $password The password to validate.
     * @return bool Returns true if the password is valid, false if the authentication fails.
     */
    public function isPasswordValid(string $username, string $password, ?string $twoFaCode = ''): bool
    {
        try {
            $restClient = new Http_client(['base_url' => $this->config->item('rest_server')]);
            $restClient->setupAuth($username, $password);
            $restClient->get('/', [Cf_RestInstance::CF_2FA_TOKEN_HEADER => $twoFaCode]);
            return true;
        } catch (HttpClient_Unauthorized $exception) {
            return false;
        }
    }

    public function resetPassword($id)
    {
        $this->load->model('ProfileModel');
        $this->load->helper('string');
        $generatedPassword = random_string(len: 14);
        try {
            $this->update_user($id, ['password' => $generatedPassword], [self::CF_PASSWORD_RESET_HEADER => 'true']);
            if ($this->getRestClient()->lastStatus() == 202) {
                $this->ProfileModel->setAdditionalData($id, ProfileModel::FIRST_lOGIN_AFTER_RESET_KEY, data: true);
                return $generatedPassword;
            }
            return false;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                'change_password ' .
                    get_class($e) .
                    '  ' .
                    $e->getMessage() .
                    ' ' .
                    $e->getFile() .
                    ' line:' .
                    $e->getLine(),
            );
            throw $e;
        }
    }
}
