<?php

use idoit\Component\FeatureManager\FeatureManager;
use idoit\Component\Helper\Unserialize;
use idoit\Module\Cmdb\Model\Ci\Table\Config;
use idoit\Module\Cmdb\Model\Ci\Table\Property;

/**
 * i-doit
 *
 * System settings.
 *
 * @package     i-doit
 * @subpackage  Modules
 * @author      Niclas Potthast <npotthast@i-doit.org>
 * @author      Dennis Stücken <dstuecken@i-doit.org>
 * @copyright   synetics GmbH
 * @license     http://www.i-doit.com/license
 */
class isys_module_system_settings extends isys_module implements isys_module_interface
{
    public const DISPLAY_IN_MAIN_MENU = false;

    // Define, if this module shall be displayed in the named menus.
    public const DISPLAY_IN_SYSTEM_MENU = true;
    public const TENANT_WIDE            = 'Tenant-wide';
    public const SYSTEM_WIDE            = 'System-wide';
    public const USER                   = 'User';

    private static $internalConfigurationKeys = [
        'plain' => [
            'system.start-of-end',
            'cmdb.objtype-',
            'cmdb.object-browser.C__',
            'cmdb.base-object-list.config.C__',
            'cmdb.base-object-table.config.C__',
            'cmdb.default-object-table.sql.C__',
            'cmdb.multilist-table.config.',
            'cmdb.default-object-list.config.',
        ],
        'regexp' => [
            '~cmdb\.obj-(\d+)-(\d+)\.table-columns~',
            '~cmdb\.obj-(\d+)-(\d+)-(\d+)\.table-columns~',
            '~cmdb\.obj-([a-zA-Z0-9_]+)\.table-columns~',
            '~cmdb\.objtype\.([a-zA-Z0-9_]+)\.specific-cat-position~',
        ]
    ];

    /**
     * @var bool
     */
    protected static $m_licenced = true;

    /**
     * Callback function for construction of breadcrumb navigation.
     *
     * @param $p_gets
     *
     * @return array|null
     * @throws isys_exception_database
     */
    public function breadcrumb_get(&$p_gets)
    {
        $page = $p_gets[C__GET__SETTINGS_PAGE];

        $tenantSettingsMapping = [
            'tenant-settings' => $this->language->get('LC__MODULE__SYSTEM__OVERVIEW', [isys_application::instance()->tenant->name]),
            'expert-settings' => $this->language->get('LC__MODULE__SYSTEM__TREE__TENANT_SETTINGS__EXPERT_SETTINGS'),
        ];

        if (isset($tenantSettingsMapping[$page])) {
            return [
                [
                    $this->language->get('LC__MODULE__SYSTEM__TREE__TENANT_SETTINGS', [isys_application::instance()->tenant->name]) => [
                        C__GET__MODULE_ID     => C__MODULE__SYSTEM,
                        C__GET__SETTINGS_PAGE => 'system-overview'
                    ]
                ],
                [
                    $tenantSettingsMapping[$page] => null
                ]
            ];
        }

        if ($page === 'cmdb-status') {
            return [
                [
                    $this->language->get('LC__MODULE__SYSTEM__TREE__PREDEFINED_CONTENT') => [
                        C__GET__MODULE_ID     => C__MODULE__SYSTEM,
                        C__GET__MODULE_SUB_ID => C__MODULE__SYSTEM_SETTINGS,
                        C__GET__SETTINGS_PAGE => 'cmdb-status'
                    ]
                ],
                [
                    $this->language->get('LC__CMDB__TREE__SYSTEM__SETTINGS_SYSTEM__CMDB_STATUS') => null
                ]
            ];
        }

        return null;
    }

    /**
     * @return $this
     * @throws isys_exception_auth
     * @throws isys_exception_database
     */
    public function start()
    {
        $l_navbar = isys_component_template_navbar::getInstance();
        $l_gets = isys_module_request::get_instance()->get_gets();
        $l_posts = isys_module_request::get_instance()->get_posts();
        $database = isys_application::instance()->container->get('database');

        $l_navbar->set_active(true, C__NAVBAR_BUTTON__EDIT);

        if (isys_glob_get_param('navMode') == C__NAVMODE__EDIT) {
            $l_navbar->set_selected(true, C__NAVBAR_BUTTON__EDIT);
            $l_navbar->set_active(true, C__NAVBAR_BUTTON__SAVE);
            $l_navbar->set_active(true, C__NAVBAR_BUTTON__CANCEL);
        }

        /**
         * @desc handle navmode actions
         */
        if (isset($l_posts['navMode'])) {
            switch ($l_posts['navMode']) {
                case C__NAVMODE__SAVE:
                    switch ($_GET[C__GET__SETTINGS_PAGE]) {
                        case 'cmdb-status':
                            isys_tenantsettings::set('system.mydoit.show_filter', $l_posts['C__SETTING__STATUS__SHOW_FILTER']);

                            // Save status.
                            $l_status_dao = new isys_cmdb_dao_status($database);

                            if (!empty($_POST['delStatus'])) {
                                foreach (explode(',', $_POST['delStatus']) as $l_delStatus) {
                                    $l_status_dao->delete_status($l_delStatus);
                                }
                            }

                            if (is_array($_POST['status_title'])) {
                                foreach ($_POST['status_title'] as $l_id => $l_title) {
                                    $l_const = $_POST['status_const'][$l_id];
                                    $l_color = $_POST['status_color'][$l_id];

                                    $l_status_dao->save($l_id, $l_const, $l_title, $l_color);
                                }
                            }

                            if (is_array($_POST['new_status_title'])) {
                                foreach ($_POST['new_status_title'] as $l_id => $l_title) {
                                    $l_const = $_POST['new_status_const'][$l_id];
                                    $l_color = $_POST['new_status_color'][$l_id];

                                    $l_status_dao->create($l_const, $l_title, $l_color);
                                }
                            }

                            isys_notify::success($this->language->get('LC__UNIVERSAL__SUCCESSFULLY_SAVED'));
                            break;

                        case 'expert-settings':
                        case 'tenant-settings':
                            if (isset($_POST['settings'][self::TENANT_WIDE]) && is_array($_POST['settings'][self::TENANT_WIDE])) {
                                $tenantDefinitions = isys_tenantsettings::getRawDefinition();
                                foreach ($_POST['settings'][self::TENANT_WIDE] as $l_key => $l_value) {
                                    if ($_POST['remove_settings'][self::TENANT_WIDE][$l_key] === '1') {
                                        isys_tenantsettings::remove($l_key);
                                    } else {
                                        // This is used for passwords.
                                        if (isset($_POST['settings__action'][self::TENANT_WIDE][$l_key]) && $_POST['settings__action'][self::TENANT_WIDE][$l_key] == isys_smarty_plugin_f_password::PASSWORD_UNCHANGED) {
                                            continue;
                                        }
                                        $settingBeforeSet = isys_tenantsettings::get($l_key);
                                        isys_tenantsettings::set($l_key, isys_glob_html_entity_decode($l_value));
                                        $changed = $settingBeforeSet !== isys_tenantsettings::get($l_key);

                                        if ($changed && isset($tenantDefinitions[$l_key]['callback']) && is_callable($tenantDefinitions[$l_key]['callback'])) {
                                            call_user_func($tenantDefinitions[$l_key]['callback'], $l_value);
                                        }
                                    }
                                }
                            }

                            if (isset($_POST['settings'][self::USER]) && is_array($_POST['settings'][self::USER])) {
                                foreach ($_POST['settings'][self::USER] as $l_key => $l_value) {
                                    if ($_POST['remove_settings'][self::USER][$l_key] === '1') {
                                        isys_usersettings::remove($l_key);
                                    } else {
                                        // This is used for passwords.
                                        if (isset($_POST['settings__action'][self::USER][$l_key]) && $_POST['settings__action'][self::USER][$l_key] == isys_smarty_plugin_f_password::PASSWORD_UNCHANGED) {
                                            continue;
                                        }

                                        isys_usersettings::set($l_key, isys_glob_html_entity_decode($l_value));
                                    }
                                }
                            }

                            // Expert Settings
                            if (isset($_POST['custom_settings']) && FeatureManager::isFeatureActive('expert-settings')) {
                                $l_callmap = [
                                    self::TENANT_WIDE => 'isys_tenantsettings',
                                    self::USER        => 'isys_usersettings'
                                ];

                                // Custom
                                foreach ($_POST['custom_settings']['key'] as $l_index => $l_key) {
                                    if ($l_key && $l_key != '') {
                                        $l_value = isset($_POST['custom_settings']['value'][$l_index]) ? $_POST['custom_settings']['value'][$l_index] : null;
                                        $l_type = $_POST['custom_settings']['type'][$l_index] ?: null;

                                        if ($l_type !== null && $l_value !== null && isset($l_callmap[$l_type])) {
                                            call_user_func([$l_callmap[$l_type], 'set'], $l_key, isys_glob_html_entity_decode($l_value));
                                        }
                                    }
                                }
                            }

                            $signales = isys_application::instance()->container->get('signals');

                            // @See ID-4990
                            isys_cache::keyvalue()->flush();

                            // @See ID-4990 flush system cache
                            $signales->emit('system.afterFlushSystemCache');

                            isys_notify::success($this->language->get('LC__UNIVERSAL__SUCCESSFULLY_SAVED'));
                            break;
                    }
                    break;

                case C__NAVMODE__DELETE:
                    break;

                case C__NAVMODE__NEW:
                    break;
            }
        }

        if ($l_gets[C__GET__SETTINGS_PAGE] === 'cmdb-status') {
            $this->processCmdbStatusSettings();
        } elseif ($l_gets[C__GET__SETTINGS_PAGE] === 'expert-settings') {
            $this->processExpertSettings();
        } elseif ($l_gets[C__GET__SETTINGS_PAGE] === 'tenant-settings') {
            $this->processTenantSettings();
        }

        return $this;
    }

    /**
     * @param isys_module_request $p_req
     *
     * @return Boolean
     */
    public function init(isys_module_request $p_req)
    {
        return true;
    }

    /**
     * CMDB Status
     */
    private function processCmdbStatusSettings()
    {
        $database = isys_application::instance()->container->get('database');

        $l_status_complete = [];

        // Check rights
        isys_auth_system_globals::instance()
            ->cmdbstatus(isys_auth::VIEW);

        $l_dao_cmdb = new isys_cmdb_dao_status($database);
        $l_status_dao = $l_dao_cmdb->get_cmdb_status();

        while ($l_row = $l_status_dao->get_row()) {
            if ($l_row['isys_cmdb_status__editable'] && !in_array($l_row['isys_cmdb_status__const'], ['C__CMDB_STATUS__IN_OPERATION', 'C__CMDB_STATUS__INOPERATIVE'], true)) {
                $l_row['isys_cmdb_status__color'] = isys_helper_color::unifyHexColor((string)$l_row['isys_cmdb_status__color']);

                $l_status_complete[] = $l_row;
            }
        }

        $l_rules['C__SETTING__STATUS__SHOW_FILTER']['p_arData'] = get_smarty_arr_YES_NO();
        $l_rules['C__SETTING__STATUS__SHOW_FILTER']['p_strSelectedID'] = isys_tenantsettings::get('system.mydoit.show_filter', 1);

        $l_navbar = isys_component_template_navbar::getInstance();
        $l_navbar->set_active(false, C__NAVBAR_BUTTON__NEW)
            ->set_active(false, C__NAVBAR_BUTTON__PURGE);

        if (isys_glob_get_param(C__GET__NAVMODE) != C__NAVMODE__EDIT) {
            $l_navbar->set_active(isys_auth_system::instance()
                ->is_allowed_to(isys_auth::EDIT, 'GLOBALSETTINGS/CMDBSTATUS'), C__NAVBAR_BUTTON__EDIT)
                ->set_visible(true, C__NAVBAR_BUTTON__EDIT);
        } else {
            $l_navbar->set_active(false, C__NAVBAR_BUTTON__EDIT)
                ->set_visible(false, C__NAVBAR_BUTTON__EDIT);
        }

        isys_application::instance()->container->get('template')
            ->assign('content_title', $this->language->get('LC__CMDB__TREE__SYSTEM__SETTINGS_SYSTEM__CMDB_STATUS'))
            ->assign('cmdb_status', $l_status_complete)
            ->smarty_tom_add_rules('tom.content.bottom.content', $l_rules)
            ->include_template('contentbottomcontent', 'content/bottom/content/module__settings__status.tpl');
    }

    /**
     * @return void
     * @throws isys_exception_auth
     * @throws isys_exception_database
     */
    private function processExpertSettings(): void
    {
        if (!FeatureManager::isFeatureActive('expert-settings')) {
            return;
        }

        // Check rights
        isys_auth_system::instance()
            ->check(isys_auth::SUPERVISOR, 'SYSTEM');

        isys_component_template_navbar::getInstance()
            ->set_active(true, C__NAVBAR_BUTTON__SAVE)
            ->set_visible(false, C__NAVBAR_BUTTON__EDIT)
            ->set_save_mode('quick');

        $l_definition = isys_settings::get_definition();
        ksort($l_definition);

        $l_tenant_settings = isys_tenantsettings::get();
        $l_tenant_definition = isys_tenantsettings::get_definition();
        ksort($l_tenant_settings);

        $l_user_settings = isys_usersettings::get();
        $l_settingsCombined = [];

        foreach ($l_tenant_settings as $l_key => $l_value) {
            if (!$this->isInternalSetting($l_key, $l_value)) {
                $l_settingsCombined[self::TENANT_WIDE][$l_key] = isys_glob_htmlentities($l_value);
            }
        }

        foreach ($l_user_settings as $l_key => $l_value) {
            if (!$this->isInternalSetting($l_key, $l_value)) {
                $l_settingsCombined[self::USER][$l_key] = isys_glob_htmlentities($l_value);
            }
        }

        isys_application::instance()->container->get('template')
            ->assign('bShowCommentary', false)
            ->assign('content_title', $this->language->get('LC__MODULE__SYSTEM__TREE__TENANT_SETTINGS__EXPERT_SETTINGS'))
            ->assign('settings', $l_settingsCombined)
            ->include_template('contentbottomcontent', 'modules/system_settings/expert.tpl');
    }

    /**
     * @return void
     * @throws isys_exception_auth
     * @throws isys_exception_database
     */
    private function processTenantSettings(): void
    {
        $systemSettings = isys_application::instance()->container->get('settingsSystem');

        // Check rights
        isys_auth_system::instance()
            ->check(isys_auth::SUPERVISOR, 'SYSTEM');

        isys_component_template_navbar::getInstance()
            ->set_active(true, C__NAVBAR_BUTTON__SAVE)
            ->set_visible(false, C__NAVBAR_BUTTON__EDIT)
            ->set_save_mode('quick');

        $l_tenant_settings = isys_tenantsettings::get();
        $l_tenant_definition = isys_tenantsettings::get_definition();
        ksort($l_tenant_settings);

        // @see  ID-6829  This code will encode the array to JSON.
        if (is_array($l_tenant_settings['ldap.config'])) {
            $l_tenant_settings['ldap.config'] = json_encode($l_tenant_settings['ldap.config'], JSON_PRETTY_PRINT);
        }

        // @see ID-10957 Fetch system setting, if tenant setting is not (yet) set.
        foreach ($l_tenant_definition as $tenantSettings) {
            foreach ($tenantSettings as $key => $definition) {
                if (!isset($l_tenant_settings[$key]) || $l_tenant_settings[$key] === '') {
                    $l_tenant_settings[$key] = isys_settings::get($key);
                }
            }
        }

        // @see ID-11319 Hide sections that only contain hidden settings.
        $l_tenant_definition = array_filter(
            $l_tenant_definition,
            fn ($settings) => count(array_filter(
                $settings,
                fn ($setting) => !isset($setting['hidden']) || !$setting['hidden']
            ))
        );

        // @see ID-9557 Set the proper default, if not set... Might this make sense for all settings?
        if (!isset($l_tenant_settings['cmdb.registry.object_type_sorting'])) {
            $l_tenant_settings['cmdb.registry.object_type_sorting'] = $systemSettings->get('cmdb.registry.object_type_sorting', C__CMDB__VIEW__OBJECTTYPE_SORTING__AUTOMATIC);
        }

        isys_component_template_navbar::getInstance()
            ->append_button('LC_UNIVERSAL__EXPAND_ALL', 'expand-all', [
                'icon'                => 'axialis/user-interface/angle-down-small.svg',
                'js_onclick'          => '',
                'navmode'             => 'expand-all'
            ])
            ->append_button('LC_UNIVERSAL__COLLAPSE_ALL', 'collapse-all', [
                'icon'                => 'axialis/user-interface/angle-up-small.svg',
                'js_onclick'          => '',
                'navmode'             => 'collapse-all'
            ]);

        isys_application::instance()->container->get('template')
            ->activate_editmode()
            ->assign('bShowCommentary', false)
            ->assign('content_title', $this->language->get('LC__MODULE__SYSTEM__OVERVIEW', [isys_application::instance()->tenant->name]))
            ->assign('systemWideKey', self::TENANT_WIDE)
            ->assign('definition', $l_tenant_definition)
            ->assign('settings', $l_tenant_settings)
            ->assign('showPasswordReset', true)
            ->assign('smtpConfigured', trim(isys_settings::get('system.email.smtp-host', '')) !== '')
            ->assign('smtpConfigurationUrl', './admin/?req=settings')
            ->include_template('contentbottomcontent', 'modules/system_settings/index.tpl');
    }

    /**
     * @param string $settingKey
     * @param mixed  $settingValue
     *
     * @return bool
     */
    private function isInternalSetting($settingKey, $settingValue)
    {
        try {
            foreach (self::$internalConfigurationKeys['plain'] as $key) {
                if (strpos($settingKey, $key) !== false) {
                    // If we find a "internal" configuration jump out immediately!
                    return true;
                }
            }

            // @see ID-00000 Skip additional internal settings, based on REGEXP.
            foreach (self::$internalConfigurationKeys['regexp'] as $key) {
                if (preg_match($key, $settingKey)) {
                    return true;
                }
            }

            if (is_scalar($settingValue)) {
                if (!$settingValue) {
                    return false;
                }

                // @see ID-9433 Only allow specific classes to be unserialized.
                // @see ID-10724 Check if the string starts with 'O:' before trying to unserialize it.
                if (str_starts_with($settingValue, 'O:') && !Unserialize::toObject($settingValue, [Config::class, Property::class]) instanceof stdClass) {
                    return true;
                }

                if (isys_format_json::is_json($settingValue) && !is_scalar(isys_format_json::decode($settingValue))) {
                    return true;
                }
            }
        } catch (Exception $e) {
        }

        return false;
    }
}
