<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows\Session;

use Exception;
use isys_application;
use isys_component_database;
use isys_component_session;

class UserSwitcher
{
    public function __construct(
        private \isys_component_session $session,
        private isys_component_database $db
    )
    {
    }

    /**
     * @todo add permission for flows to only allow it to run if user has rights for flows
     *
     * @return bool
     */
    private function canExecute()
    {
        return $this->session->is_logged_in();
    }

    /**
     * @param callable $function
     * @param int      $userId
     *
     * @return void
     * @throws Exception
     */
    public function runAs(callable $function, int $userId): void
    {
        if (!$this->canExecute()) {
            return;
        }

        $previousUserId = (int)$this->session->get_user_id();

        try {
            $this->switchUserById($userId);
            $function();
        } finally {
            $this->switchUserById($previousUserId);
            // @see FLOW-346 regenerate session data in case it has been resetted by a previous call
            $this->session->get_session_data();
        }
    }

    /**
     * @param int $userId
     *
     * @return bool
     * @throws Exception
     */
    public function switchUserById(int $userId): bool
    {
        // @todo add authorization check for flows so that only with a specific permission in flows this method can be called
        if (!$this->canExecute()) {
            return false;
        }

        $recordStatus = C__RECORD_STATUS__NORMAL;

        $query = "SELECT
                isys_obj__id,
                isys_cats_person_list__isys_obj__id,
                isys_cats_person_list__title,
                isys_cats_person_list__first_name,
                isys_cats_person_list__last_name,
                isys_catg_mail_addresses_list__title AS isys_cats_person_list__mail_address
            FROM isys_obj
            INNER JOIN isys_cats_person_list ON isys_cats_person_list__isys_obj__id = isys_obj__id
            LEFT JOIN isys_catg_mail_addresses_list ON isys_catg_mail_addresses_list__isys_obj__id = isys_obj__id AND isys_catg_mail_addresses_list__primary = 1
            WHERE isys_obj__id = '{$this->db->escape_string($userId)}'
            AND isys_cats_person_list__disabled_login = 0
            AND isys_obj__status = '{$recordStatus}'
            LIMIT 1;";
        $result = $this->db->query($query);
        $data = $this->db->fetch_row_assoc($result);

        if (!$data) {
            return false;
        }

        $this->switchUserInSession((int)$data['isys_obj__id'], 'switch');
        $objectData = $this->db->fetch_row_assoc($this->querySessionData($this->db, null, $this->session->get_session_id()));
        // Store session data (very important to do this here)
        $this->session->write_userdata($objectData);
        if (method_exists(isys_component_session::class, 'resetSessionData')) {
            $this->session->resetSessionData();
        }

        try {
            // Delete expired sessions
            $this->session->delete_expired_sessions();
        } catch (Exception $e) {
            isys_glob_display_error($e->getMessage());
            die();
        }

        return true;
    }

    /**
     * @param isys_component_database $p_db
     * @param                         $p_username
     * @param                         $p_session_id
     * @param                         $p_condition
     *
     * @return mixed
     */
    private function querySessionData(isys_component_database &$p_db, $p_username = null, $p_session_id = null, $p_condition = '')
    {
        $l_condition = $p_condition;

        if ($p_session_id) {
            $l_condition .= ' AND isys_user_session__php_sid = "' . $p_db->escape_string($p_session_id) . '"';
        }

        if ($p_username) {
            $l_condition .= ' AND ( BINARY LOWER(isys_cats_person_list__title) = LOWER(\'' . $p_db->escape_string($p_username) . '\'))';
        }

        if ($p_db->is_field_existent('isys_cats_person_list', 'isys_cats_person_list__disabled_login')) {
            $l_condition .= ' AND isys_cats_person_list__disabled_login = 0';
        }

        $query = 'SELECT isys_obj__id, isys_obj__title, isys_cats_person_list.*, isys_catg_mail_addresses_list__title AS isys_cats_person_list__mail_address, isys_user_session.*
            FROM isys_cats_person_list
            INNER JOIN isys_obj ON isys_cats_person_list__isys_obj__id = isys_obj__id
            LEFT JOIN isys_catg_mail_addresses_list ON isys_catg_mail_addresses_list__isys_obj__id = isys_obj__id AND isys_catg_mail_addresses_list__primary = 1
            LEFT JOIN isys_user_session ON isys_user_session__isys_obj__id = isys_obj__id
            WHERE isys_obj__status = ' . ((int)C__RECORD_STATUS__NORMAL) .
            $l_condition . ';';

        return $p_db->query($query);
    }

    /**
     * @param int         $p_userid
     * @param string|null $modus
     *
     * @return bool
     * @throws Exception
     */
    private function switchUserInSession(int $p_userid, ?string $modus = null): bool
    {
        $session = isys_application::instance()->container->get('session');

        $values = [
            "isys_user_session__isys_obj__id = '{$this->db->escape_string($p_userid)}'"
        ];
        $this->m_user_id = $p_userid;

        if ($modus) {
            $values[] = "isys_user_session__modus = '{$this->db->escape_string($modus)}'";
        }

        $l_query = "UPDATE isys_user_session
            SET " . implode(',', $values) . "
            WHERE isys_user_session__php_sid = '{$this->session->get_session_id()}';";

        return (bool)$this->db->query($l_query);
    }
}
