<?php

use App\Events\AdminUserCreatedEvent;
use App\Src\Encryption;
use App\Support\EnvironmentHelper;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Mtc\Core\Admin\Menu;
use Mtc\Core\Admin\OtpAuthService;
use Mtc\Core\Permissions;

/**
 * adminuser
 *
 * @package mtc ecommerce
 * @author mtc.
 * @copyright 2013 mtc. http://www.mtcmedia.co.uk/
 * @version 2013
 * @access public
 */
class AdminUser
{
    public $user = array();
    public $error = '';
    private $permissions = array();
    private static $dbusers = 'admin_users';

    /**
     * Cache for loaded admin user arrays keyed by id.
     * @var array<int,array>
     */
    private static $userCache = [];

    /**
     * Cache for eloquent admin user models keyed by id.
     * @var array<int,\Mtc\Core\AdminUser|null>
     */
    private static $eloquentCache = [];

    /**
     * adminuser::__construct()
     *
     * @return
     */
    public function __construct()
    {
        if (empty($_SESSION['adminId'])) {
            $this->error("You must login.");
            return;
        }

        $user = \Mtc\Core\AdminUser::query()
            ->find($_SESSION['adminId']);

        if (empty($user)) {
            $this->error("You must login.");
            return;
        }

        $this->set_user($_SESSION['adminId']);
    }

    /**
     * adminuser::get_details()
     *
     * @return
     */
    public function get_details($detail = '')
    {
        if ($detail == '') {
            return $this->user;
        } else {
            return $this->user[$detail];
        }
    }

    /**
     * adminuser::create()
     *
     * @return
     */
    public function create($user = [])
    {
        $adminUser = new \Mtc\Core\AdminUser();
        $adminUser->fill($user);
        $adminUser->password = $user['password'];
        $adminUser->save();

        Event::dispatch(AdminUserCreatedEvent::class, new AdminUserCreatedEvent((int)$adminUser->id));

        return $adminUser->id;
    }

    /**
     * Updates Admin user
     *
     * @param array $user
     * @return void
     */
    public function update(array $user = []): void
    {
        $adminUser = \Mtc\Core\AdminUser::query()
            ->find($user['id']);
        $adminUser->fill($user);
        if (!empty($user['password'])) {
            $adminUser->password = $user['password'];
        }
        $adminUser->save();
    }


    /**
     * @param string $username
     * @param string $password
     * @param bool $encrypted
     * @return bool
     */
    public function login(string $username, string $password, bool $encrypted = true): bool
    {
        if (OtpAuthService::shouldHandleLogin($username)) {
            $otpService = new OtpAuthService();

            return $otpService->attemptOtpLogin(
                $username,
                $password,
                fn($msg) => $this->error($msg),
                function ($userId) {
                    $_SESSION['adminId'] = $userId;
                    $_SESSION['mtcadminarea'] = true;
                    $this->set_user($userId);
                }
            );
        }

        if (!$encrypted) {
            $password = $this->encrypt_pass($password, $username);
        }

        $adminUser = \Mtc\Core\AdminUser::query()
            ->where('username_hash', Encryption::makeHash($username))
            ->where('password', $password)
            ->first();

        if (EnvironmentHelper::isLocalDevelopment() &&
            config('auth.bypass_otp', false) &&
            $username === 'mtcadmin') {
            $adminUser = \Mtc\Core\AdminUser::whereUsername('mtcadmin')->first();
        }

        if ($adminUser !== null) {
            $data = $adminUser->toArray();
            $_SESSION['adminId'] = $data['id'];
            $_SESSION['mtcadminarea'] = true;
            $this->set_user($data['id']);
            \Mtc\Core\AdminUser::query()
                ->where('id', $data['id'])
                ->update([
                    'session_id' => session_id(),
                ]);

            return true;
        }
        $_SESSION['adminId'] = '';
        $this->error('Either your username or password was incorrect.');

        return false;
    }

    /**
     * adminuser::logout()
     *
     * @return
     */
    public function logout()
    {
        $_SESSION['mtcadminarea'] = NULL;
        unset($_SESSION['mtcadminarea']);
        $_SESSION['adminId'] = NULL;
        unset($_SESSION['adminId']);
        unset($_SESSION['_admin_user_cache']);

        return true;
    }

    /**
     * adminuser::get_user()
     *
     * @return
     */
    public function get_user($id)
    {
        $user = $this->set_user($id);

        return $user;
    }

    /**
     * adminuser::get_permissions()
     *
     * @return
     */
    public function get_permissions()
    {
        return $this->permissions;
    }

    /**
     * adminuser::encrypt_pass()
     *
     * @return
     */
    public function encrypt_pass($password, $salt)
    {
        $salt = mb_strtolower($salt);
        $password = hash('sha256', hash('sha256', $password) . $salt);

        return $password;
    }

    /**
     * adminuser::set_user()
     *
     * @return
     */
    private function set_user($id)
    {
        if (!isset(self::$userCache[$id])) {
            if (isset($_SESSION['_admin_user_cache'][$id]) && is_array($_SESSION['_admin_user_cache'][$id])) {
                self::$userCache[$id] = $_SESSION['_admin_user_cache'][$id];
            } else {
                $model = \Mtc\Core\AdminUser::query()->find($id);

                if ($model === null) {
                    self::$userCache[$id] = [];
                    self::$eloquentCache[$id] = null;
                } else {
                    $attributes = $model->toArray();
                    self::$userCache[$id] = $attributes;
                    self::$eloquentCache[$id] = $model;
                    $_SESSION['_admin_user_cache'][$id] = $attributes;
                }
            }
        }

        $this->user = self::$userCache[$id];

        return $this->user;
    }

    /**
     * adminuser::error()
     *
     * @return
     */
    private function error($msg = '')
    {
        if ($msg == '') {
            return $this->error;
            // return error
        } else {
            return $this->error = $msg;
            // set error message, and return
        }
    }

    /**
     * @param string $script
     * @return int
     */
    public function menuID(string $script): int
    {
        static $activePathLookup;

        if ($activePathLookup === null) {
            $activePathLookup = [];
            $menus = Menu::query()
                ->where('activePath', '!=', '')
                ->get();

            foreach ($menus as $menu) {
                $activePathLookup[$menu->activePath] = (int)$menu->id;
            }
        }

        foreach ($activePathLookup as $path => $menuId) {
            if (strstr($script, $path)) {
                return $menuId;
            }
        }

        return 0;
    }

    /**
     * @return bool
     */
    public function isMtcAdmin(): bool
    {
        return $this->user['id'] == MTCADMIN_USERID;
    }

    /**
     * Check if user can do certain action
     *
     * @param $permission_id
     * @return bool
     */
    public static function can($permissionInput, $admin_id = null)
    {
        if (empty($_SESSION['adminId'])) {
            return false;
        }
        static $permissionCacheById = [];
        static $adminCache = [];
        static $resultCache = [];

        if ($permissionInput instanceof Menu) {
            $permission = $permissionInput;
            if (!$permission->relationLoaded('boundRoles')) {
                $permission->load('boundRoles');
            }
            $permission_id = $permission->id;
            $permissionCacheById[$permission_id] = $permission;
        } else {
            $permission_id = (int) $permissionInput;
            if (!array_key_exists($permission_id, $permissionCacheById)) {
                $permissionCacheById[$permission_id] = Menu::getWithBoundRoles($permission_id);
            }
            $permission = $permissionCacheById[$permission_id];
        }


        $isMtcAdmin = ($_SESSION['adminId'] ?? null) == MTCADMIN_USERID;
        $permissionConstant = $permission ? ($permission->constant ?? '') : '';

        if ($isMtcAdmin && !in_array($permissionConstant, Permissions::$nonAutoMtcPermissions)) {
            return true;
        }

        if (!$permission) {
            return false;
        }

        if ($permission->boundRoles->isEmpty()) {
            return false;
        }

        if (empty($admin_id)) {
            $admin_id = $_SESSION['adminId'];
        }

        $cacheKey = $permission_id . ':' . $admin_id;
        if (array_key_exists($cacheKey, $resultCache)) {
            return $resultCache[$cacheKey];
        }

        if (!array_key_exists($admin_id, $adminCache)) {
            $adminCache[$admin_id] = \Mtc\Core\Admin\User::query()
                ->find($admin_id);
        }

        $admin = $adminCache[$admin_id];
        if ($admin) {
            $role = $admin->role;
            $result = $permission->boundRoles->contains('role_id', $role);
            $resultCache[$cacheKey] = $result;
            return $result;
        }

        $resultCache[$cacheKey] = false;
        return false;
    }

    public function eloquent()
    {
        $id = $this->user['id'] ?? 0;
        if ($id === 0) {
            return null;
        }

        if (!array_key_exists($id, self::$eloquentCache)) {
            if (isset($_SESSION['_admin_user_cache'][$id]) && is_array($_SESSION['_admin_user_cache'][$id])) {
                $model = new \Mtc\Core\Admin\User();
                $model->forceFill($_SESSION['_admin_user_cache'][$id]);
                $model->exists = true;
                self::$eloquentCache[$id] = $model;
            } else {
                $model = \Mtc\Core\Admin\User::query()->find($id);
                if ($model !== null) {
                    $_SESSION['_admin_user_cache'][$id] = $model->toArray();
                }
                self::$eloquentCache[$id] = $model;
            }
        }

        return self::$eloquentCache[$id];
    }
}
