<?php

use idoit\Component\Helper\Purify;
use idoit\Component\Helper\Unserialize;
use Symfony\Component\Filesystem\Filesystem;

/**
 * i-doit
 *
 * Quick configuration wizard module
 *
 * @package     i-doit
 * @subpackage  Modules
 * @copyright   synetics GmbH
 * @license     http://www.i-doit.com/license
 * @since       1.0
 */
class isys_module_quick_configuration_wizard extends isys_module implements isys_module_interface
{
    /** @var isys_quick_configuration_wizard_dao */
    protected $m_dao;

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

    /**
     * Method for retrieving the breadcrumb part.
     *
     * @param array $gets
     * @return array|null
     * @throws Exception
     */
    public function breadcrumb_get(&$gets)
    {
        return [
            [
                'LC__MODULE__SYSTEM__TREE__DATA_STRUCTURE' => [
                    C__GET__MODULE_ID     => C__MODULE__SYSTEM,
                    C__GET__MODULE_SUB_ID => C__MODULE__QCW,
                ],
            ],
            [
                'LC__CMDB__TREE__SYSTEM__CMDB_CONFIGURATION__QCW' => null,
            ],
        ];
    }

    /**
     * Static factory method for instant method chaining.
     *
     * @static
     * @return  isys_module_quick_configuration_wizard
     */
    public static function factory()
    {
        return new self;
    }

    /**
     * Retrieve the profiles path - this differes between tenants.
     *
     * @static
     * @return  string
     */
    public static function getProfilePath(): string
    {
        global $g_mandator_info;

        $directory = BASE_DIR . 'imports/qcw/' . Purify::formatFilename($g_mandator_info['isys_mandator__dir_cache']) . '/';

        if (!mkdir($directory, 0775) && !is_dir($directory)) {
            throw new \RuntimeException(sprintf('The mandatory directory "%s" could not be created, please check the filesystem rights.', $directory));
        }

        return $directory;
    }

    /**
     * Gets all profile files in an array
     *
     * @static
     * @return array
     */
    public static function get_config_files(): array
    {
        $profileDirectory = rtrim(self::getProfilePath(), '/');

        return array_map('basename', glob("{$profileDirectory}/*.xml"));
    }

    /**
     * Deletes the specified profile file
     *
     * @static
     * @param string $filename
     * @return bool
     */
    public static function delete_config_file(string $filename): bool
    {
        if (str_ends_with($filename, '.xml')) {
            $filepath = self::getProfilePath() . '/' . $filename;

            try {
                $filesystem = new Filesystem();

                if (!$filesystem->exists($filepath)) {
                    return false;
                }

                $filesystem->remove($filepath);
            } catch (Throwable $e) {
                return false;
            }
        }

        return true;
    }

    //////////////////////////////////
    //			START EXPORT		//
    //////////////////////////////////

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

        return $this;
    }

    /**
     * @return $this
     * @throws isys_exception_auth
     */
    public function start()
    {
        $l_tree = $this->m_userrequest->get_menutree();

        /** @var isys_component_template $template */
        $template = isys_application::instance()->container->get('template');

        /** @var isys_component_template_language_manager $language */
        $language = isys_application::instance()->container->get('language');

        isys_module_system::getAuth()->check(isys_auth::VIEW, 'GLOBALSETTINGS/QCW');
        $isAllowedToExecute = isys_module_system::getAuth()->is_allowed_to(isys_auth::EXECUTE, 'GLOBALSETTINGS/QCW');

        $objectTypeSorting = isys_tenantsettings::get('cmdb.registry.object_type_sorting', C__CMDB__VIEW__OBJECTTYPE_SORTING__AUTOMATIC);

        $template
            ->assign('isAllowedToExecute', (int)$isAllowedToExecute)
            ->assign('content_title', $language->get('LC__CMDB__TREE__SYSTEM__CMDB_CONFIGURATION__QCW'))
            ->assign('encType', 'multipart/form-data')
            ->assign('obj_type_sorting_alphabetical', $objectTypeSorting == C__CMDB__VIEW__OBJECTTYPE_SORTING__AUTOMATIC)
            ->assign('menu_tree', $l_tree->process($_GET[C__GET__TREE_NODE]))
            ->include_template('contentbottomcontent', self::getPath() . 'templates/quick_configuration_wizard.tpl');

        if ($isAllowedToExecute) {
            isys_component_template_navbar::getInstance()
                ->append_button('LC__MODULE__QCW__PROFILE_POPUP', 'profile_popup', [
                    'tooltip'    => 'LC__MODULE__QCW__PROFILE_POPUP_TOOLTIP',
                    'icon'       => 'axialis/basic/windows.svg',
                    'js_onclick' => "get_popup('qcw_profiles', '', '800', '550', {}, '');"
                ]);
        }

        return $this;
    }

    /**
     * This function generates an xml file with the current objecttype configuration.
     *
     * @param string $configurationTitle
     * @return string
     * @throws Exception
     */
    public function generate_export(string $configurationTitle)
    {
        $configurationTitle = $configurationTitle === '' ? 'default' : $configurationTitle;

        $l_dir = self::getProfilePath();
        $l_filename = Purify::formatFilename("{$configurationTitle}.xml");
        $l_full_filename = $l_dir . $l_filename;

        if (is_writable($l_dir)) {
            $l_export = $this->export_current_config($l_full_filename);
            if (!$l_export) {
                throw new isys_exception_general('An error occurred while exporting the config.');
            }
        } else {
            throw new isys_exception_general('Please check the rights of the folder "' . $l_dir . '".');
        }

        return $l_full_filename;
    }

    /**
     * This function creates the XML file.
     *
     * @param string $targetFilepath
     * @return int
     */
    public function export_current_config(string $targetFilepath)
    {
        $l_xml = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n" .
            "<isys_export>\n" .
            $this->export_objtype_groups() .
            "</isys_export>";

        return isys_file_put_contents($targetFilepath, $l_xml);
    }

    /**
     * This function appends the objecttype groups to the xml file
     *
     * @todo    Use a XML class - NOT string concatenation...
     * @author  Van Quyen Hoang <qhoang@i-doit.org>
     */
    public function export_objtype_groups()
    {
        $l_root = '';
        $l_object_type_group_result = $this->m_dao->load_object_type_groups();

        while ($l_objtype_group = $l_object_type_group_result->get_row()) {
            $l_root .= "\t<objecttypegroup>\n";
            $l_root .= $this->format_objtype_group_head($l_objtype_group);
            $l_root .= $this->export_objtypes($l_objtype_group['isys_obj_type_group__const']);
            $l_root .= "\t</objecttypegroup>\n";
        }

        return $l_root;
    }

    /**
     * This function adds the objecttype group header information to the xml file
     *
     * @param $p_data
     *
     * @return string
     */
    public function format_objtype_group_head($p_data)
    {
        $l_root = "\t\t<head>\n";

        foreach ($p_data as $l_key => $l_data) {
            $l_tag = str_replace('isys_obj_type_group__', '', $l_key);
            $l_root .= "\t\t\t<" . $l_tag . ">" . $l_data . "</" . $l_tag . ">\n";
        }
        $l_root .= "\t\t</head>\n";

        return $l_root;
    }

    //////////////////////////////////
    //			END EXPORT			//
    //////////////////////////////////

    //////////////////////////////////
    //			START IMPORT		//
    //////////////////////////////////

    /**
     * This function appends the objecttypes to the xml file
     *
     * @param $p_objtype_group_const
     *
     * @return string
     */
    public function export_objtypes($p_objtype_group_const)
    {
        $l_objtypes = $this->m_dao->load_objecttypes($p_objtype_group_const, true);
        $l_root = "\t\t<objecttypes>\n";
        foreach ($l_objtypes as $l_row) {
            $l_root .= "\t\t\t<objecttype>\n";
            $l_root .= $this->format_objtype_head($l_row);
            $l_root .= $this->export_assigned_categories($l_row['isys_obj_type__const']);
            $l_root .= "\t\t\t</objecttype>\n";
        }
        $l_root .= "\t\t</objecttypes>\n";

        return $l_root;
    }

    //////////////////////////////////
    //			END IMPORT			//
    //////////////////////////////////

    /**
     * This function adds the objecttype header information to the xml file
     *
     * @param $p_data
     *
     * @return string
     */
    public function format_objtype_head($p_data)
    {
        $l_root = "\t\t\t\t<head>\n";

        $l_show_in_tree = '0';
        $l_status = '1';

        foreach ($p_data as $l_key => $l_data) {
            if (strpos($l_key, 'isys_obj_type__') !== false) {
                $l_tag = str_replace('isys_obj_type__', '', $l_key);
                switch ($l_tag) {
                    case 'isysgui_cats__id':
                        $l_data = $p_data['isysgui_cats__const'];
                        break;
                    case 'show_in_tree':
                        $l_show_in_tree = $l_data;
                        break;
                    case 'status':
                        $l_status = $l_data;
                        break;
                    default:
                        break;
                }
                $l_root .= "\t\t\t\t\t<" . $l_tag . ">" . $l_data . "</" . $l_tag . ">\n";
            }
        }

        $l_query = 'UPDATE isys_obj_type SET isys_obj_type__show_in_tree = \'' . $l_show_in_tree . '\', isys_obj_type__status = \'' . $l_status .
            '\', isys_obj_type__isys_obj_type_group__id = ' . '(SELECT isys_obj_type_group__id FROM isys_obj_type_group WHERE isys_obj_type_group__const = \'' .
            $p_data['isys_obj_type_group__const'] . '\') ' . 'WHERE isys_obj_type__const = \'' . $p_data['isys_obj_type__const'] . '\'';

        $l_root .= "\t\t\t\t\t<query><![CDATA[" . $l_query . "]]></query>\n";

        $l_root .= "\t\t\t\t</head>\n";

        return $l_root;
    }

    /**
     * This function appends the categories to the xml file
     *
     * @param $p_objtype_const
     *
     * @return string
     */
    public function export_assigned_categories($p_objtype_const)
    {
        $l_categories = $this->m_dao->load_assigned_categories($p_objtype_const);
        $l_root = "\t\t\t\t<assigned_catgories>";

        foreach ($l_categories as $l_category) {
            $l_root .= $l_category . ",";
        }
        $l_root = rtrim($l_root, ',');
        $l_root .= "</assigned_catgories>\n";

        if (isset($this->m_dao->m_assigned_custom_categories[$p_objtype_const])) {
            $l_root .= "\t\t\t\t<assigned_custom_catgories>";
            $l_root .= "<![CDATA[";
            foreach ($this->m_dao->m_assigned_custom_categories[$p_objtype_const] as $l_category) {
                $l_root .= serialize($this->m_dao->m_custom_categories[$l_category]) . "||";
            }
            $l_root = rtrim($l_root, '||') . "]]>";
            $l_root .= "</assigned_custom_catgories>\n";
        }

        return $l_root;
    }

    /**
     * Loads the config files and reorganizes the objecttype structure
     *
     * @param string|array $p_filename_selection
     * @param isys_caching $p_caching
     *
     * @author Van Quyen Hoang <qhoang@i-doit.org>
     */
    public function load_config($p_filename_selection, isys_caching $p_caching)
    {
        global $g_absdir;

        try {
            $l_dir = self::getProfilePath();

            if (is_array($p_filename_selection)) {
                $l_files_arr = $p_filename_selection;
            } else {
                $l_files_arr = [$p_filename_selection];
            }

            $this->m_dao->disable_all_objecttypes();

            $l_libxml = new isys_import_xml();
            $l_dao_custom = isys_custom_fields_dao::instance(isys_application::instance()->container->get('database'));

            $l_res = $l_dao_custom->get_data();
            $l_custom_categories = [];
            if ($l_res->num_rows()) {
                while ($l_row = $l_res->get_row()) {
                    $l_custom_categories[$l_row['isysgui_catg_custom__const']] = $l_row['isysgui_catg_custom__id'];
                }
            }

            foreach ($l_files_arr as $l_filename_item) {
                $l_filename = $l_filename_item;
                $l_full_filename = $l_dir . $l_filename;

                $l_libxml->load_xml_file($l_full_filename);

                $xmlData = $l_libxml->get_xml_data();
                $l_xml_data = array_shift($xmlData);

                foreach ($l_xml_data as $l_arr) {
                    foreach ($l_arr as $l_objtype_group) {
                        $l_objtype_group_const = '';
                        $l_objtype_group_id = '';
                        $l_objtype_group_data = [];

                        $l_head = $l_objtype_group[0]['head'];
                        foreach ($l_head as $l_key => $l_value) {
                            // skip the id field
                            if ($l_key == 'id') {
                                continue;
                            }

                            if ($l_key == 'const') {
                                $l_objtype_group_const = $l_value;
                                $l_objtype_group_id = $this->m_dao->get_objtype_group_id($l_objtype_group_const);
                            }
                            $l_objtype_group_data['isys_obj_type_group__' . $l_key] = $l_value;
                        }
                        if (!$l_objtype_group_id) {
                            $l_objtype_group_id = $this->m_dao->add_new_objtype_group($l_objtype_group_data);

                            // Just in case if the object type group id is = 0
                            if ((int)$l_objtype_group_id === 0) {
                                $l_objtype_group_id = $this->m_dao->get_objtype_group_id($l_objtype_group_const);
                            }
                        }

                        /* Set status of object group */
                        $this->m_dao->objtypegroup_change_status($l_objtype_group_const, (bool)($l_head['status'] - 1));

                        $l_objtypes = $l_objtype_group[1]['objecttypes'];

                        if (is_array($l_objtypes)) {
                            foreach ($l_objtypes as $l_objtype) {
                                $l_head = $l_objtype['objecttype'][0]['head'];
                                $l_string_assigned_categories = trim($l_objtype['objecttype']['assigned_catgories']);
                                $l_string_assigned_custom_categories = '';
                                $l_assigned_custom_categories = [];
                                $l_objtype_const = '';
                                $l_assignment_query = '';

                                foreach ($l_head as $l_key => $l_value) {
                                    switch ($l_key) {
                                        case 'id':
                                            continue 2;

                                        case 'default_template':
                                            // Check if template exists and has status = C__RECORD_STATUS__TEMPLATE
                                            if ($l_value > 0) {
                                                if ((bool)$this->m_dao->retrieve('SELECT isys_obj__id FROM isys_obj WHERE isys_obj__id = ' .
                                                    $this->m_dao->convert_sql_id($l_value) . ' AND isys_obj__status = ' .
                                                    $this->m_dao->convert_sql_int(C__RECORD_STATUS__TEMPLATE) . ';')
                                                    ->num_rows()) {
                                                    $l_objtype_data['isys_obj_type__' . $l_key] = $l_value;
                                                }
                                            }
                                            break;

                                        case 'const':
                                            $l_objtype_const = $l_value;
                                            $l_objtype_data['isys_obj_type__' . $l_key] = $l_value;
                                            break;

                                        case 'query':
                                            $l_assignment_query = $l_value;
                                            break;

                                        default:
                                            $l_objtype_data['isys_obj_type__' . $l_key] = $l_value;
                                            break;
                                    }
                                }
                                $l_objtype_data['isys_obj_type__isys_obj_type_group__id'] = $l_objtype_group_id;

                                $l_check = $this->m_dao->check_objecttype_by_const($l_objtype_const);
                                if (!$l_check) {
                                    $l_objtype_id = $this->m_dao->add_new_objtype($l_objtype_data);
                                } else {
                                    $l_objtype_id = $this->m_dao->get_objtype_id($l_objtype_const);
                                }

                                // Custom categories
                                if (isset($l_objtype['objecttype']['assigned_custom_catgories'])) {
                                    $l_string_assigned_custom_categories = trim($l_objtype['objecttype']['assigned_custom_catgories']);
                                    $l_assigned_custom_categories_res = $l_dao_custom->get_assignments(null, $l_objtype_id);
                                    while ($l_row_cc = $l_assigned_custom_categories_res->get_row()) {
                                        $l_assigned_custom_categories[$l_row_cc['isysgui_catg_custom__const']] = true;
                                    }
                                }

                                // Update objtype group assignment
                                $this->m_dao->update($l_assignment_query);

                                $l_assigned_categories_cached = $p_caching->get($l_objtype_const);
                                if (!$l_assigned_categories_cached) {
                                    $l_assigned_categories_cached = $this->m_dao->cache_objtype($l_objtype_id, $l_objtype_const, $p_caching);
                                }
                                $l_new_assigned_categories = [];
                                $l_new_assigned_custom_categories = [];

                                if ($l_string_assigned_categories != '') {
                                    $l_assigned_categories = [];
                                    $l_raw_assigned_categories = explode(',', $l_string_assigned_categories);
                                    foreach ($l_raw_assigned_categories as $l_category_const) {
                                        if (strpos($l_category_const, 'C__CATG__CUSTOM_FIELDS') !== false) {
                                            continue;
                                        }

                                        $l_cat_id = $p_caching->get($l_category_const);
                                        if (empty($l_cat_id)) {
                                            $p_caching->add($l_category_const, ($l_cat_id = $this->m_dao->get_catg_id($l_category_const)))
                                                ->save();
                                        }

                                        // Skip categories which were not found for example if a category from a module does not exist then we cannot assign it
                                        if ($l_cat_id) {
                                            $l_assigned_categories[$l_category_const] = $l_cat_id;
                                        }
                                    }

                                    if (!$l_assigned_categories_cached) {
                                        $l_new_assigned_categories = $l_assigned_categories;
                                        $l_assigned_categories_cached = [];
                                    } else {
                                        foreach ($l_assigned_categories as $l_cat_const => $l_cat_id) {
                                            if (!in_array($l_cat_const, $l_assigned_categories_cached)) {
                                                $l_new_assigned_categories[] = $l_cat_const;
                                            }
                                        }
                                    }
                                }

                                // Handle custom categories
                                if ($l_string_assigned_custom_categories != '') {
                                    $l_raw_assigned_categories = explode('||', $l_string_assigned_custom_categories);

                                    foreach ($l_raw_assigned_categories as $l_category_info) {
                                        $l_category_data = Unserialize::toArray($l_category_info);
                                        $l_category_const = $l_category_data[0];
                                        $l_category_title = $l_category_data[1];
                                        $l_category_multivalue = $l_category_data[2];
                                        $l_category_config = Unserialize::toArray($l_category_data[3]);

                                        if (!isset($l_custom_categories[$l_category_const]) && !defined($l_category_const)) {
                                            // Custom category does not exist create it
                                            $l_custom_id = $l_dao_custom->create($l_category_title, $l_category_config, 0, 0, $l_category_multivalue, $l_category_const);
                                            $l_custom_categories[$l_category_const] = $l_custom_id;

                                            // Assign custom category to object type
                                            $l_dao_custom->assign($l_custom_id, $l_objtype_id);
                                        } else {
                                            // Custom category exists
                                            $l_custom_id = (isset($l_custom_categories[$l_category_const])) ? $l_custom_categories[$l_category_const] : (defined($l_category_const) ? constant($l_category_const) : 0);

                                            // Check if its assigned to the object type
                                            if (!$l_assigned_custom_categories[$l_category_const]) {
                                                // Assign custom category to object type
                                                $l_dao_custom->assign($l_custom_categories[$l_category_const], $l_objtype_id);
                                            }
                                        }

                                        $l_cat_id = $p_caching->get($l_category_const);
                                        if ($l_custom_id > 0) {
                                            if (empty($l_cat_id)) {
                                                $p_caching->add($l_category_const, $l_custom_id)
                                                    ->save();
                                            }

                                            if (!in_array($l_category_const, $l_assigned_categories_cached)) {
                                                $l_new_assigned_custom_categories[] = $l_category_const;
                                            }
                                        }
                                    }
                                }

                                // Assign only global categories categories only if it is needed
                                if (count($l_new_assigned_categories)) {
                                    // Merge categories from selected profile and add them to the cache
                                    $l_assigned_categories_cached = array_merge($l_assigned_categories_cached, $l_new_assigned_categories);
                                    $p_caching->add($l_objtype_const, $l_assigned_categories_cached)
                                        ->save();

                                    // Assign only global categories
                                    $this->m_dao->assign_categories_to_objecttype($l_objtype_id, $l_new_assigned_categories);
                                }
                            }
                        }
                    }
                }
            }
            $this->m_dao->apply_update();
        } catch (Exception $e) {
            isys_notify::warning($e->getMessage());
        }
    }

    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();

        $this->m_dao = new isys_quick_configuration_wizard_dao(isys_application::instance()->container->get('database'));
    }
}
