<?php

use idoit\Component\Helper\Unserialize;
use idoit\Module\CustomFields\PropertyTypes;
use idoit\Module\Pro\Model\AttributeSettings;

/**
 * i-doit
 *
 * Custom fields.
 *
 * @package     i-doit
 * @subpackage  Modules
 * @copyright   synetics GmbH
 * @license     http://www.i-doit.com/license
 */
class isys_module_custom_fields extends isys_module
{
    // Define, if this module shall be displayed in the named menus.
    const LABEL_POSITION_LEFT = 'left';
    const LABEL_POSITION_TOP = 'top';

    /**
     * @var  isys_module_request
     */
    private $m_userrequest;

    /**
     * Method for retrieving the breadcrumb part.
     *
     * @param array $gets
     *
     * @return array|null
     */
    public function breadcrumb_get(&$gets)
    {
        return [
            [
                $this->language->get('LC__MODULE__SYSTEM__TREE__DATA_STRUCTURE') => [
                    C__GET__MODULE_ID     => C__MODULE__SYSTEM,
                    C__GET__MODULE_SUB_ID => C__MODULE__CUSTOM_FIELDS
                ]
            ],
            [
                $this->language->get('LC__CMDB__TREE__SYSTEM__CUSTOM_CATEGORIES') => null
            ]
        ];
    }

    /**
     * List generation.
     *
     * @return  mixed
     */
    public function get_category_list()
    {
        $l_dao = new isys_custom_fields_dao(isys_application::instance()->container->get('database'));

        $l_data = $l_dao->get_data();

        if (is_countable($l_data) && count($l_data)) {
            $listDao = new isys_cmdb_dao_list_catg_custom_fields(isys_application::instance()->container->get('database'));
            $listDao->set_properties([
                'isysgui_catg_custom__title'            => [
                    C__PROPERTY__INFO => [
                        C__PROPERTY__INFO__TITLE => 'LC__CMDB__CATG__CATEGORY'
                    ]
                ],
                'isysgui_catg_custom__list_multi_value' => [
                    C__PROPERTY__INFO => [
                        C__PROPERTY__INFO__TITLE => 'LC__CMDB__CUSTOM_CATEGORIES__MULTIVALUE'
                    ]
                ],
                'isys__field_count'                     => [
                    C__PROPERTY__INFO => [
                        C__PROPERTY__INFO__TITLE => $this->language->get('LC__CMDB__CATG__QUANTITY') . ' ' . $this->language->get('LC__CMDB__CATG__CUSTOM_FIELDS')
                    ]
                ],
                'isys__assignment'                      => [
                    C__PROPERTY__INFO => [
                        C__PROPERTY__INFO__TITLE => 'LC__CMDB__CATG__CUSTOM_FIELDS__ASSIGNED_OBJECT_TYPES'
                    ]
                ]
            ]);

            $listLink = isys_helper_link::create_url([
                C__GET__MODULE_ID      => defined_or_default('C__MODULE__SYSTEM'),
                C__GET__MODULE_SUB_ID  => defined_or_default('C__MODULE__CUSTOM_FIELDS'),
                C__GET__TREE_NODE      => $_GET[C__GET__TREE_NODE],
                'id'                   => '[{isysgui_catg_custom__id}]'
            ]);

            $l_objList = new isys_component_list(null, $l_data);
            $l_objList->set_row_modifier($this, 'row_mod');
            $l_objList->config(
                [
                    'isysgui_catg_custom__title'            => 'LC__CMDB__CATG__CATEGORY',
                    'isysgui_catg_custom__list_multi_value' => 'LC__CMDB__CUSTOM_CATEGORIES__MULTIVALUE',
                    'isys__field_count'                     => $this->language->get('LC__CMDB__CATG__QUANTITY') . ' ' . $this->language->get('LC__CMDB__CATG__CUSTOM_FIELDS'),
                    'isys__assignment'                      => 'LC__CMDB__CATG__CUSTOM_FIELDS__ASSIGNED_OBJECT_TYPES',
                ],
                $listLink,
                '[{isysgui_catg_custom__id}]'
            );

            $l_objList->setRouteParams(['id' => null]);
            $l_objList->createTempTable();

            return $l_objList->getTempTableHtml();
        }

        isys_component_template_navbar::getInstance()
            ->set_active(false, C__NAVBAR_BUTTON__PURGE);

        return '<div class="p10">' . $this->language->get('LC__CMDB__FILTER__NOTHING_FOUND_STD') . '</div>';
    }

    /**
     * Initializes the module.
     *
     * @param isys_module_request $p_req
     *
     * @return $this
     */
    public function init(isys_module_request $p_req)
    {
        $this->m_userrequest = &$p_req;

        return $this;
    }

    /**
     * Row modifier.
     *
     * @param  array &$p_row
     */
    public function row_mod(&$p_row)
    {
        $imageDir = isys_application::instance()->www_path . 'images/axialis/basic/';
        $l_add_dots = false;
        $config = Unserialize::toArray($p_row['isysgui_catg_custom__config']);
        $p_row['isys__field_count'] = is_countable($config) ? count($config) : 0;

        $l_assigns = isys_custom_fields_dao::instance(isys_application::instance()->container->get('database'))
            ->get_assignments($p_row['isysgui_catg_custom__id']);
        $l_obj_types = [];

        while ($l_a = $l_assigns->get_row()) {
            if (count($l_obj_types) > 10) {
                $l_add_dots = true;
                break;
            }

            $l_obj_types[] = $this->language->get($l_a['isys_obj_type__title']);
        }

        sort($l_obj_types);

        if ($l_add_dots) {
            $l_obj_types[] = '...';
        }

        $p_row['isysgui_catg_custom__title'] = isys_glob_htmlentities($p_row['isysgui_catg_custom__title']);
        $p_row['isys__assignment'] = implode(', ', $l_obj_types);
        $p_row['isysgui_catg_custom__list_multi_value'] = '<div class="display-flex align-items-center">' .
            ($p_row['isysgui_catg_custom__list_multi_value'] ?
                '<img src="' . $imageDir . 'symbol-ok.svg" class="mr5" /><span class="text-green">' . $this->language->get('LC__UNIVERSAL__YES') . '</span>' :
                '<img src="' . $imageDir . 'symbol-cancel.svg" class="mr5" /><span class="text-red">' . $this->language->get('LC__UNIVERSAL__NO') . '</span>') .
            '</div>';
    }

    /**
     * @param array  $configuration
     * @param string $categoryConstant
     *
     * @return array[]
     */
    public function prepare_api_example_for_config($configuration, $categoryConstant)
    {
        $apiExample = [
            'method'  => 'cmdb.category.save',
            'params'  => [
                'language' => isys_application::instance()->language,
                'apikey'   => 'your-key',
                'category' => 'C__CATG__CUSTOM_FIELDS_' . $categoryConstant,
                'object'   => 1234,
                'data'     => []
            ],
            'id'      => 1,
            'version' => '2.0'
        ];

        if (is_array($configuration)) {
            foreach ($configuration as $key => $field) {
                // @see  API-201 / ID-7100  Skip properties that don't contain data.
                if (in_array($field['type'], ['html', 'hr', 'script'], true)) {
                    continue;
                }

                // Specific case for 'report'.
                if ($field['type'] === 'f_popup' && $field['popup'] === 'report_browser') {
                    continue;
                }

                $apiExample['params']['data'][$field['type'] . '_' . $key] = $this->getExampleContent($field);
            }
        }

        $return = [
            'create' => $apiExample,
            'read' => $apiExample
        ];

        $return['read']['method'] = 'cmdb.category.read';
        unset($return['read']['params']['data']);

        return $return;
    }

    /**
     * @param array $field
     *
     * @return array|int|string|null
     */
    private function getExampleContent(array $field)
    {
        $multiselection = isset($field['multiselection']) && $field['multiselection'];

        switch ($field['type']) {
            case 'f_time':
                return date('H:i');

            case 'f_numeric':
                return (string) (rand(1000, 9999) / 100);

            case 'f_wysiwyg':
                return 'HTML textual-content';

            case 'f_link':
            case 'f_password':
            case 'f_text':
            case 'f_textarea':
                return 'textual-content';

            case 'f_dialog':
                if ($field['extra'] === 'yes-no') {
                    return rand(0, 1);
                }

                return rand(1, 10);

            case 'f_popup':
                if ($field['popup'] === 'dialog_plus' || $field['popup'] === 'checkboxes') {
                    $randomInt = rand(1, 10);

                    if ($multiselection) {
                        return [$randomInt, $randomInt + rand(1, 10), $randomInt + rand(11, 20)];
                    }

                    return $randomInt;
                }

                if ($field['popup'] === 'browser_object' || $field['popup'] === 'file') {
                    $randomInt = rand(100, 200);

                    if ($multiselection) {
                        return [$randomInt, $randomInt + rand(1, 100), $randomInt + rand(101, 200)];
                    }

                    return $randomInt;
                }

                if ($field['popup'] === 'calendar') {
                    if ($field['default'] == '1') {
                        return date('Y-m-d H:i:s');
                    }

                    return date('Y-m-d');
                }

                // no break
            default:
                return null;
        }
    }

    /**
     * Process custom field configuration
     */
    public function start()
    {
        $systemAuth = isys_module_system::getAuth();

        $systemAuth->is_allowed_to(isys_auth::VIEW, 'GLOBALSETTINGS/CUSTOMFIELDS');

        try {
            $navbar = isys_component_template_navbar::getInstance();
            $template = isys_application::instance()->container->get('template');
            $database = isys_application::instance()->container->get('database');

            $l_dao = new isys_custom_fields_dao($database);

            $l_process_filter = (isset($_POST['filter']) && !empty($_POST['filter']));

            // Delete a custom category.
            if ($_POST[C__GET__NAVMODE] == C__NAVMODE__PURGE) {
                if (isset($_POST["id"]) && is_array($_POST["id"])) {
                    foreach ($_POST["id"] as $l_id) {
                        $l_dao->delete($l_id);
                    }
                }
            }

            $l_id = 0;
            if ($_POST[C__GET__NAVMODE] != C__NAVMODE__NEW) {
                $l_id = $_POST[C__GET__ID] ?: $_GET[C__GET__ID];
                if (is_array($l_id)) {
                    $l_id = $l_id[0];
                }
            }

            // Switch back to list on cancel.
            if ($_POST[C__GET__NAVMODE] == C__NAVMODE__CANCEL || $_POST[C__GET__NAVMODE] == C__NAVMODE__PURGE) {
                unset($_GET["id"]);
                $l_id = null;
            }

            $l_edit_right = $systemAuth->is_allowed_to(isys_auth::EDIT, 'GLOBALSETTINGS/CUSTOMFIELDS');
            $l_delete_right = $systemAuth->is_allowed_to(isys_auth::DELETE, 'GLOBALSETTINGS/CUSTOMFIELDS');

            $relationData = [];
            $labelPosition = self::LABEL_POSITION_LEFT;

            $l_dao_rel = new isys_cmdb_dao_category_g_relation(isys_application::instance()->container->get('database'));
            $relationTypeData = $l_dao_rel->get_relation_types_as_array(null);

            // Necessary logic for displaying a sorted list of relation types.
            foreach ($relationTypeData as $relationTypeId => $relationType) {
                $relationData[strtolower($relationType['title_lang'])] = [
                    'id'    => $relationTypeId,
                    'title' => $relationType['title_lang']
                ];
            }

            ksort($relationData);

            $propertyTypes = PropertyTypes::getAll($this->language);

            // Sort the properties.
            usort($propertyTypes, function ($a, $b) {
                return strcasecmp($a['title'], $b['title']);
            });

            $template
                ->assign('propertyTypes', $propertyTypes)
                ->assign('id', null);

            // Init vars
            $l_multivalued = 0;
            $l_title = $l_selected = $l_constant = null;

            // New or Edit.
            if (isset($l_id) && $l_id > 0 && $_POST[C__GET__NAVMODE] != C__NAVMODE__NEW) {
                $l_data = $l_dao->get_data($l_id);
                $l_row = $l_data->get_row();
                $labelPosition = $l_row['isysgui_catg_custom__label_position'];
                $l_title = $l_row['isysgui_catg_custom__title'];
                $l_multivalued = $l_row['isysgui_catg_custom__list_multi_value'];
                $l_constant = $l_row['isysgui_catg_custom__const'];

                if (strpos($l_constant, 'C__CATG__CUSTOM_FIELDS_') === 0) {
                    $l_constant = substr($l_constant, 23);
                } else {
                    isys_notify::warning($this->language->get('LC__CMDB__CUSTOM_CATEGORIES_OLD_CONSTANT_WARNING'), ['sticky' => true]);
                }

                $l_row['isysgui_catg_custom__config'] = str_replace("'", '"', $l_row['isysgui_catg_custom__config']);
                $l_config = $l_example_config = Unserialize::toArray($l_row['isysgui_catg_custom__config']);

                foreach ($l_config as $l_key => $l_value) {
                    // @see  API-201 / ID-7100  Skip properties that don't contain data.
                    if (in_array($l_value['type'], ['html', 'hr', 'script'], true)) {
                        unset($l_example_config[$l_key]);
                    }
                }

                $hiddenAttributeIds = [];

                if (class_exists(AttributeSettings::class)) {
                    $hiddenAttributeIds = AttributeSettings::getAttributeVisibilityForCategories('C__CATG__CUSTOM_FIELDS_' . $l_constant);

                    // Strip the 'C__CATG__CUSTOM__' from the left of the string in order to correctly hide them in the configuration GUI,
                    $hiddenAttributeIds = array_map(
                        fn ($item) => str_starts_with($item, 'C__CATG__CUSTOM__') ? substr($item, strlen('C__CATG__CUSTOM__')) : $item,
                        $hiddenAttributeIds
                    );
                }

                $template
                    ->assign('attributeVisibilityConfigCustom', $hiddenAttributeIds)
                    ->assign('category_config', $l_config)
                    ->assign('apiExample', $this->prepare_api_example_for_config($l_config, $l_constant))
                    ->assign('apiExampleConfig', $l_example_config)
                    ->assign('id', $l_id)
                    ->assign('entryCount', $l_dao->count($l_id))
                    ->assign('valueCount', $l_dao->count_values($l_id));

                $navbar
                    ->set_active($l_edit_right, C__NAVBAR_BUTTON__SAVE)
                    ->set_active(true, C__NAVBAR_BUTTON__CANCEL)
                    ->set_visible(true, C__NAVBAR_BUTTON__SAVE);
            } elseif ($_POST[C__GET__NAVMODE] == C__NAVMODE__NEW) {
                if ($l_edit_right) {
                    $_GET[C__CMDB__GET__EDITMODE] = 1;
                }

                $navbar
                    ->set_active($l_edit_right, C__NAVBAR_BUTTON__SAVE)
                    ->set_active(true, C__NAVBAR_BUTTON__CANCEL)
                    ->set_visible(true, C__NAVBAR_BUTTON__SAVE);
            } else {
                $l_list = $this->get_category_list();

                $template
                    ->assign('g_list', $l_list)
                    ->smarty_tom_add_rule('tom.content.bottom.buttons.*.p_bInvisible=1');

                $navbar
                    ->set_active($l_delete_right, C__NAVBAR_BUTTON__PURGE)
                    ->set_active($l_edit_right, C__NAVBAR_BUTTON__EDIT)
                    ->set_active($l_edit_right, C__NAVBAR_BUTTON__NEW)
                    ->set_visible(true, C__NAVBAR_BUTTON__PURGE)
                    ->set_visible(true, C__NAVBAR_BUTTON__EDIT)
                    ->set_visible(true, C__NAVBAR_BUTTON__NEW)
                    // We use this line to prevent the breadcrumb from loading - because this will trigger a "History.pushState()" call.
                    ->set_js_onclick(
                        "$('sort').setValue(''); $('navMode').setValue('" . C__NAVMODE__EDIT . "'); form_submit(false, false, false, true);",
                        C__NAVBAR_BUTTON__EDIT
                    );
            }

            if (!$l_edit_right) {
                $_GET[C__CMDB__GET__EDITMODE] = 0;
            }

            $l_cmdb_dao = isys_cmdb_dao::instance(isys_application::instance()->container->get('database'));
            $l_object_types = $l_cmdb_dao->get_objtype();
            $l_otypes = [];

            while ($l_row = $l_object_types->get_row()) {
                if (defined('C__OBJTYPE__GENERIC_TEMPLATE') && $l_row['isys_obj_type__id'] == C__OBJTYPE__GENERIC_TEMPLATE) {
                    // Skip generic Template.
                    continue;
                }

                if (isset($l_id) && $l_id > 0) {
                    $l_sel = $l_dao->get_assignments($l_id, $l_row['isys_obj_type__id']);
                    $l_selected = ($l_sel->num_rows() > 0);
                }

                $l_row['isys_obj_type__title'] = $this->language->get($l_row['isys_obj_type__title']);

                $l_otypes[$l_row['isys_obj_type__title']] = [
                    'val' => $l_row['isys_obj_type__title'],
                    'hid' => 0,
                    'sel' => $l_selected,
                    'id'  => $l_row['isys_obj_type__id']
                ];
            }

            ksort($l_otypes);

            // Set smarty rules
            $l_rules = [
                'category_title'    => [
                    'p_strValue' => $l_title,
                    'p_strClass' => 'input-medium'
                ],
                'object_types'      => [
                    'p_arData'   => array_values($l_otypes),
                    'p_strClass' => 'input-medium'
                ],
                'category_constant' => [
                    'p_strValue' => $l_constant
                ],
                'multivalued'       => [
                    'p_arData'        => get_smarty_arr_YES_NO(),
                    'p_strSelectedID' => $l_multivalued,
                    'p_bDbFieldNN'    => true,
                    'p_strClass'      => 'input-small'
                ],
                'label_position'    => [
                    'p_arData'        => [
                        self::LABEL_POSITION_LEFT => 'LC__UNIVERSAL__LEFT',
                        self::LABEL_POSITION_TOP  => 'LC__UNIVERSAL__TOP'
                    ],
                    'p_strSelectedID' => $labelPosition,
                    'p_bDbFieldNN'    => true,
                    'p_strClass'      => 'input-small'
                ],
                'count_of_custom_fields' => [
                    'p_strClass' => 'input-mini'
                ]
            ];
            $template
                ->assign('isAllowedToEdit', (int)$l_edit_right)
                ->assign('content_title', $this->language->get('LC__CMDB__TREE__SYSTEM__CUSTOM_CATEGORIES'))
                ->include_template('contentbottomcontent', self::getPath() . 'templates/config.tpl')
                ->smarty_tom_add_rule('tom.content.navbar.cRecStatus.p_bInvisible=1')
                ->smarty_tom_add_rules('tom.content.bottom.content', $l_rules);

            if ($l_edit_right) {
                $template->activate_editmode();
            }
        } catch (Exception $e) {
            throw $e;
        }

        return $this;
    }

    /**
     * @param int    $id
     * @param string $from
     * @param string $to
     *
     * @return void
     * @throws isys_exception_dao
     * @see ID-8404 If keys have been changed, we need to update some data and configuration.
     */
    public static function propertyKeyChanged(int $id, string $from, string $to): void
    {
        $database = isys_application::instance()->container->get('database');
        $logger = isys_application::instance()->container->get('logger');
        $tenantsettings = isys_application::instance()->container->get('settingsTenant');
        $dao = isys_custom_fields_dao::instance($database);

        // Here we change the references between 'property key' and saved data.
        $migrated = $dao->updateKeyReferences($id, $from, $to);
        $logger->info(str_replace([':migrated', ':from', ':to'], [$migrated, $from, $to], 'Migrated :migrated entries from ":from" to ":to".'));

        // Now we update object browser configurations (if any are set).
        $objectBrowserConfigurations = $tenantsettings->getLike("cmdb.object-browser.C__CATG__CUSTOM__{$from}.");

        foreach ($objectBrowserConfigurations as $key => $value) {
            // Create a setting with the new name.
            $tenantsettings->set(str_replace($from, $to, $key), $value);

            // And remove the old one.
            $tenantsettings->remove($key);
        }
    }
}
