<?php

namespace apexl\Io\modules\user\entities;

use apexl\Io\includes\Entity;
use apexl\Io\includes\System;
use apexl\Io\modules\user\entities\operators\userOperator;
use apexl\Io\modules\user\services\currentUser;

class userEntity extends Entity {

    /** @var userOperator */
    protected $operator;

    /** @var System */
    protected $system;

    /** @var array - Cache loaded roles so when doing access checks we dont hammer the database. */
    protected $cachedRoles = [];

    public function __construct()
    {
        parent::__construct('users');
        $this->setOperator(new userOperator('users'));
        $this->setSensitiveData(['password', 'salt']);
        $this->setRequiredData(['email', 'first_name', 'last_name', 'password']);
        $this->addReadOnlyField('salt')->addReadOnlyField('created');

        $this->system = System::getInstance();
    }

    /**
     * override the magic load method so we can intercept roles and unsterilise them.
     * @param $id
     * @param bool $skipAccessCheck
     * @return $this
     *@throws \Exception
     */
    public function load($id, bool $skipAccessCheck = FALSE)
    {
        if($this->system->isInstalled()) {
            parent::__call('load', [$id, $skipAccessCheck]);
            //gives us an array of role id's for easy use later.
            if (!empty($this->data['roles']) && is_string($this->data['roles'])) {
                $this->data['roles'] = unserialize($this->data['roles']);
                $this->cacheRoles();
            }
        }
        return $this;
    }

    /**
     * @param array $conditions
     * @param array $orderBy
     * @param false $limit
     * @param false $offset
     * @param bool $asEntities
     * @return array
     * @throws \Exception
     */
    public function loadMultiple(array $conditions = [], array $orderBy = [], $limit = FALSE, $offset = FALSE, bool $asEntities = TRUE): array{
        //we need to allow users with permission to bypass the default access control.
        $currentUser = (currentUser::getInstance())::getCurrentUser();
        if($currentUser->isAllowed('ViewUsers')){
            $conditions = array_merge($conditions, ["id" => ["users.id", NULL, "IS NOT"]]);
        }
        return parent::loadMultiple($conditions, $orderBy, $limit, $offset, $asEntities);
    }

    protected function cacheRoles(){
        foreach ($this->data['roles'] as $id) {
            if(!isset($this->cachedRoles[$id])){
                $roles = new roleEntity();
                $this->cachedRoles[$id] = $roles->load($id, TRUE);
            }
        }
        return $this;
    }

    public function getUsersByRoleName($name){
        $roles = new roleEntity();
        $roles->loadByName($name);
        return $this->operator->getUsersByRid($roles->id);
    }

    /**
     * Function to add an ID to an array. PHP now throws "Indirect modification of overloaded property" when trying to set Array's using magic methods
     * @param $id
     * @return $this
     */
    public function addRole($id){
        $this->data['roles'][] = $id;
        return $this;
    }

    /**
     * @return $this
     */
    public function removeRoles(){
        $this->data['roles'] = [];
        return $this;
    }

    /**
     * override the magic store method to make sure we serialise role data before saving.
     * @throws \Exception
     */
    public function store(): Entity{
        //we need to revert the serialised data after storing, so its still available as if we loaded the data
        if(isset($this->data['roles']) && !empty($this->data['roles']) && is_array($this->data['roles'])){
            $cleanVersion = $this->data['roles'];
            $this->data['roles'] = serialize($this->data['roles']);
            parent::store();
            $this->data['roles'] = $cleanVersion;
            return $this;
        }
        // if this is an array at this point, then its empty and has no values, so set it to null.
        if(is_array($this->data['roles'])){
            $this->data['roles'] = null;
        }
        parent::store();
        return $this;
    }

    public function isAllowed($permission){
        //allow user 1 to always access everything (if set in config)
        if(isset($this->config->app->permissions->enableGodUser) && $this->config->app->permissions->enableGodUser == TRUE && $this->id == 1){
            //we're God user and we're allowed access to everything.
            return TRUE;
        }
        //we have a few core permissions to account for 'standard' functionality
        switch($permission){
            case 'canDoNothing':
                return FALSE;
                break;
            case 'AllowAll':
                return TRUE;
                break;
            case 'IsLoggedIn':
                return $this->isLoggedIn();
            default:
                if(!empty($this->data['roles'])) {
                    foreach ($this->data['roles'] as $id) {
                        if(!isset($this->cachedRoles[$id])){
                            $this->cacheRoles();
                        }
                        //if we have access in any of the provided roles, then return TRUE.
                        if ($this->cachedRoles[$id]->access($permission)) {
                            return TRUE;
                        }
                    }
                }
        }


        //no access? Return FALSE;
        return FALSE;
    }

    public function getNiceName(): string{
        return $this->id !== FALSE ? $this->first_name . ' ' . $this->last_name : 'Missing User';
    }

    public function isLoggedIn(){
        return (isset($this->id) && !empty($this->id));
    }

    public function hasRole($roleName){
        return $this->data['roles'] ? in_array($roleName, $this->data['roles']) : FALSE;
    }

    public function newForgottenPasswordLink($string){
        $this->operator->newForgottenPasswordLink($this->email, $string);
    }

    protected function fieldConfig(){
        return [
            "id" => [
                'name' => "ID"
            ],
            "first_name" => [
                'name' => "First Name"
            ],
            "last_name" => [
                'name' => "Last Name"
            ],
            "active" => [
                'name' => "Active",
                'display' => function($active){
                    return $active ? 'Yes' : 'No';
                }
            ],
            "email" => [
                'name' => "Email Address"
            ],
            "password" => [
                'name' => "Password",
                'display' => function($password){
                    return 'Hidden';
                }
            ],
            "last_login" => [
                'name' => "Last Login",
                'display' => function($lastLogin){
                    return $lastLogin ? date('d-m-Y H:i:s', $lastLogin) : 'Never';
                }
            ],
            "salt" => [
                'name' => "Salt",
                'display' => function($salt){
                    return 'Hidden';
                }
            ],
            "roles" => [
                'name' => "Roles",
                'display' => function($roles){
                    $niceNames = [];
                    $roleEntity = new roleEntity();
                    if(!is_array($roles)){
                        $roles = unserialize($roles);
                    }
                    if(!empty($roles) && is_array($roles)){
                        foreach($roles as $rid){
                            $roleEntity->load($rid);
                            $niceNames[] = $roleEntity->name;
                        }
                    }
                    return !empty($niceNames) ? implode(', ', $niceNames) : 'No Roles';
                }
            ],
        ];
    }
}