<?php

namespace apexl\Io\modules\user\services;

use apexl\Config\Singleton;
use apexl\hashing\Hash;
use apexl\Io\includes\Hook;
use apexl\Io\modules\company\entities\companyEntity;
use apexl\Io\modules\user\entities\refreshTokenEntity;
use apexl\Io\modules\user\entities\roleEntity;
use apexl\Io\modules\user\entities\sessionEntity;
use apexl\Io\modules\user\entities\userEntity;
use Psr\Http\Message\ServerRequestInterface as Request;

class userTools
{
    protected $config;
    protected $nameCache = [];

    public function __construct()
    {
        $this->config = Singleton::getInstance();
    }

    public function splitNameToFirstAndLast($nameString)
    {
        $nameParts = array_filter(explode(" ", $nameString));
        $lastName = array_pop($nameParts);
        $firstName = implode(" ", $nameParts);
        $firstName = $this->titleCase($firstName);
        $lastName = $this->titleCase($lastName);

        return [$firstName, $lastName];
    }
    public function getEntityEditorName($entity, $type='modified')
    {
        $prop = $type . '_by';
        if (!isset($this->nameCache[$entity->$prop])) {
            $userEntity = new userEntity();
            $userEntity->load($entity->$prop);
            $this->nameCache[$entity->$prop] = trim($userEntity->first_name . ' ' . $userEntity->last_name);
        }
        return $this->nameCache[$entity->$prop];
    }

    protected function titleCase($string)
    {
        $word_splitters = array(' ', '-', "O'", "L'", "D'", 'St.', 'Mc');
        $lowercase_exceptions = array('the', 'van', 'den', 'von', 'und', 'der', 'de', 'da', 'of', 'and', "l'", "d'");
        $uppercase_exceptions = array('III', 'IV', 'VI', 'VII', 'VIII', 'IX');

        $string = strtolower($string);
        foreach ($word_splitters as $delimiter) {
            $words = explode($delimiter, $string);
            $newwords = array();
            foreach ($words as $word) {
                if (in_array(strtoupper($word), $uppercase_exceptions)) {
                    $word = strtoupper($word);
                } elseif (!in_array($word, $lowercase_exceptions)) {
                    $word = ucfirst($word);
                }

                $newwords[] = $word;
            }

            if (in_array(strtolower($delimiter), $lowercase_exceptions)) {
                $delimiter = strtolower($delimiter);
            }

            $string = implode($delimiter, $newwords);
        }
        return $string;
    }

    public function startLoggedInSession($user)
    {
        $sessionId = bin2hex(random_bytes(20));
        if (($token = currentUser::createJWT($user, $sessionId, $this->config->app->jwt->secret_key, $this->config->app->jwt->algorithm, ($this->config->app->jwt->lifetime ?? 3600))) !== '' && ($token = currentUser::createJWT($user, $sessionId, $this->config->app->jwt->secret_key, $this->config->app->jwt->algorithm, ($this->config->app->jwt->lifetime ?? 3600))) !== '0') {
            //now we need to update the sessions table
            $session = new sessionEntity();
            $session->uid = $user->id;
            $session->sessionId = $sessionId;
            $session->created = time();
            $session->ended = 0;
            $session->active = 1;
            $session->store();

            $user->last_login = time();
            $user->store();

            // Generate and save refresh token
            $refreshToken = bin2hex(random_bytes(20));
            $refreshTokenEntity = new refreshTokenEntity();
            $refreshTokenEntity->load($user->id);
            $refreshTokenEntity->user_id = $user->id;
            $refreshTokenEntity->token = hash('sha256', $refreshToken);
            $refreshTokenEntity->expiry = time() + $this->config->app->jwt->refresh_token_lifetime;
            $refreshTokenEntity->store();
        }

        return [$token, $refreshToken];
    }

    /**
     * Create a user with an otional company
     * @param $email
     * @param $firstName
     * @param $lastName
     * @param $password
     * @param $companyName
     * @return false
     * @throws \Exception
     */
    public function createCompanyUserWithPassword(Request $request, $email, $firstName, $lastName, $password, $companyName=null, $userActive=true, $meta=null)
    {
        $user = new userEntity();
        $user->removeRoles();
        $user->first_name = $firstName;
        $user->last_name = $lastName;
        $user->email = $email;
        if (isset($meta['remote_user_id'])) $user->remote_user_id = $meta['remote_user_id'];

        $role = new roleEntity();
        $role->loadByName('Company User');
        $user->addRole($role->id);

        if ($password ?? false) {
            $passwordHash = '';
            $passwordSalt = '';
            if (isset($password) && !empty($password)) {
                $hash = new Hash();
                $hashData = $hash->hashString($password);
                $passwordHash = $hashData->hash;
                $passwordSalt = $hashData->salt;
                unset($password);
            }
            $user->password = $passwordHash;
            $user->salt = $passwordSalt;
            $user->active = $userActive?1:0;
        }

        //Hook user pre save - allow other modules to act on the user save process, before storage.
        $user = Hook::processHook('userPreSave', $user, $request);

        if ($user->isValid()) {
            $user->store();
            //Hook user post save - allow other modules to act on the user save process, after storage.
            $user = Hook::processHook('userPostSave', $user, $request);
            if (isset($meta['company']) && $meta['company']) $companyName = $meta['company'];
            if ($companyName && class_exists('apexl\Io\modules\company\entities\companyEntity')) {
                $company = new companyEntity();
                $company->CompanyName = $companyName;
                $company->CompanyContactEmail = $email;
                $company->Active = 1;
                $company->store();
                $company->storeCompanyUser($company->id, $user->id);
            }

            return $user;
        }

        return false;
    }
}
