<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsJdisc\Model;

use idoit\Component\Helper\Unserialize;
use Idoit\Dto\Serialization\Serializer;
use idoit\Module\Cmdb\Model\Matcher\MatchDao;
use idoit\Module\SyneticsJdisc\Controller\Table\SearchParams;
use idoit\Module\SyneticsJdisc\Model\Dto\SyncProfile;
use isys_ajax_handler_jdisc;
use isys_application;
use isys_cmdb_dao;
use isys_cmdb_dao_jdisc;
use isys_component_filemanager;
use isys_component_template_language_manager;
use isys_format_json as JSON;
use isys_jdisc_dao;
use isys_module_dao;
use isys_module_synetics_jdisc;
use isys_request;
use Throwable;

class JDiscProfileDao extends AbstractDao
{
    public const PROFILE_FIELDS = [
        'isys_jdisc_profile__id' => 'id',
        'isys_jdisc_profile__title' => 'name',
        'isys_jdisc_profile__description' => 'description',
        'isys_jdisc_profile__categories' => 'categories',
        'isys_jdisc_profile__import_all_software' => 'import_all_software',
        'isys_jdisc_profile__import_software_licences' => 'import_software_licences',
        'isys_jdisc_profile__import_software_services' => 'import_software_services',
        'isys_jdisc_profile__import_all_networks' => 'import_all_networks',
        'isys_jdisc_profile__import_all_clusters' => 'import_all_clusters',
        'isys_jdisc_profile__import_all_blade_connections' => 'import_all_blade_connections',
        'isys_jdisc_profile__jdisc_server' => 'jdisc_server',
        'isys_jdisc_profile__import_custom_attributes' => 'import_custom_attributes',
        'isys_jdisc_profile__import_all_vlans' => 'import_all_vlans',
        'isys_jdisc_profile__import_type_interfaces' => 'import_type_interfaces',
        'isys_jdisc_profile__cmdb_status' => 'cmdb_status',
        'isys_jdisc_profile__use_default_templates' => 'use_default_templates',
        'isys_jdisc_profile__software_filter' => 'software_filter',
        'isys_jdisc_profile__software_filter_type' => 'software_filter_type',
        'isys_jdisc_profile__software_obj_title' => 'software_obj_title',
        'isys_jdisc_profile__isys_obj_type__id__chassis_module' => 'chassis_assigned_modules_objtype',
        'isys_jdisc_profile__update_objtype' => 'update_objtype',
        'isys_jdisc_profile__update_obj_title' => 'update_obj_title',
        'isys_jdisc_profile__chassis_module_update_objtype' => 'chassis_module_update_objtype',
        'isys_jdisc_profile__use_simple_database_mod' => 'use_simple_database_mod',
        'isys_jdisc_profile__network_adresses' => 'network_adresses',
        'isys_jdisc_profile__import_type_dhcp_addresses' => 'import_type_dhcp_addresses',
        'isys_jdisc_profile__software_filter_type_regexp' => 'software_filter_type_regexp',
        'isys_jdisc_profile__import_cloud_subscriptions' => 'import_cloud_subscriptions',
        'isys_jdisc_profile__import_connection_endpoint' => 'import_connection_endpoint',
        'isys_jdisc_profile__import_create_cloud_users' => 'import_create_cloud_users',
        'isys_jdisc_profile__adopt_location' => 'adopt_location',
        'isys_jdisc_profile__last_scanned_device' => 'last_scanned_device',
        'isys_jdisc_profile__resolve_fqdn' => 'resolve_fqdn',
        'isys_jdisc_profile__network_import_rules' => 'network_import_rules',
        'isys_jdisc_profile__network_import_filter' => 'network_import_filter',
        'isys_jdisc_profile__mac_filter_type' => 'mac_filter_type',
        'isys_jdisc_profile__mac_filter' => 'mac_filter',
        'isys_jdisc_profile__software_location_mode' => 'software_location_mode',
        'isys_jdisc_profile__software_location_id' => 'software_location',
        'isys_jdisc_profile__network_location_mode' => 'network_location_mode',
        'isys_jdisc_profile__network_location_id' => 'network_location',
        'isys_jdisc_profile__cmdb_status_created' => 'cmdb_status_created',
        'isys_jdisc_profile__column_settings' => 'column_settings',
        'isys_jdisc_profile__conf_server_id' => 'conf_server',
        'isys_jdisc_profile__conf_group_id' => 'conf_group_id',
        'isys_jdisc_profile__conf_filter' => 'conf_filter',
        'isys_jdisc_profile__conf_import_mode' => 'conf_import_mode',
        'isys_jdisc_profile__conf_overwrite_ip' => 'conf_overwrite_ip',
        'isys_jdisc_profile__conf_logging' => 'conf_logging',
        'isys_jdisc_profile__conf_search_index' => 'conf_search_index',
    ];

    public const SORTING_MAP = [
        'id'           => 'isys_jdisc_profile__id',
        'name'         => 'isys_jdisc_profile__title',
        'jdisc_server' => 'isys_jdisc_profile__jdisc_server',
    ];

    private $skippedFields = [
        'mac_filter_type',
        'network_import_rules',
        'software_filter_type',
        'software_filter_type_regexp',
        'network_location_mode',
        'software_location_mode',
        'conf_group_id',
        'conf_filter',
        'conf_import_mode',
        'conf_overwrite_ip',
        'conf_logging',
        'conf_search_index',
    ];

    /**
     * @param string|null       $condition
     * @param SearchParams|null $searchParams
     *
     * @return array
     * @throws \isys_exception_database
     */
    public function getData(?string $condition = null, ?SearchParams $searchParams = null): array
    {
        $result = [];
        $mapping = self::PROFILE_FIELDS;
        array_walk($mapping, function (&$item, $key) {
            $item = $key . ' AS ' . $this->convert_sql_text($item);
        });

        $query = 'SELECT ' . implode(',', $mapping) . ' FROM isys_jdisc_profile WHERE TRUE ';
        if (null !== $condition) {
            $query .= ' AND ' . $condition;
        }

        if ($searchParams) {
            if ($searchParams->getSearchTerm()) {
                $searchTerm = $this->convert_sql_text('%' . $searchParams->getSearchTerm() . '%');
                $query .= " AND (isys_jdisc_profile__title LIKE {$searchTerm}) ";
            }

            $sortId = $searchParams->getSort()?->getId() ?? null;
            if ($sortId && isset(self::SORTING_MAP[$sortId])) {
                $direction = $searchParams->getSort()?->isDesc() ? 'desc' : 'asc';
                $query .= ' ORDER BY ' . self::SORTING_MAP[$sortId] . ' ' . $direction;
            }

            $query .= " LIMIT {$searchParams->getPerPage()} OFFSET {$searchParams->getOffset()}";
        }

        $commonData = $this->prepareCommonData();
        $daoResult  = $this->retrieve($query . ';');
        while ($row = $daoResult->get_row()) {
            $profile = $this->processRow($row, $commonData);
            $result[] = $profile;
        }

        return $result;
    }

    /**
     * @param int $id
     * @return SyncProfile|null
     */
    public function get(int $id): ?SyncProfile
    {
        $mapping = self::PROFILE_FIELDS;
        array_walk($mapping, function (&$item, $key) {
            $item = $key . ' AS ' . $this->convert_sql_text($item);
        });
        $fields = implode(',', $mapping);
        $row    = $this->retrieve(
            "SELECT {$fields} FROM isys_jdisc_profile WHERE isys_jdisc_profile__id = {$id}"
        )->get_row();
        if (!$row) {
            return null;
        }

        $commonData = $this->prepareCommonData();
        return $this->processRow($row, $commonData);
    }

    /**
     * @param string|null $condition
     *
     * @return int
     * @throws \isys_exception_database
     */
    public function getCount(?string $condition = null): int
    {
        $daoResult = $this->retrieve('SELECT count(1) as count FROM isys_jdisc_profile WHERE TRUE ' . ($condition ?: '') . ';');
        $result = $daoResult->get_row_value('count');
        if ($result === null) {
            return 0;
        }
        return intval($result);
    }

    /**
     * @param int|null $id
     * @deprecated
     *
     * @return \isys_component_dao_result
     * @throws \isys_exception_database
     */
    public function getProfileList(mixed $id = null)
    {
        $query = "SELECT
            isys_jdisc_profile__id,
            isys_jdisc_profile__title,
            isys_jdisc_profile__description,
            isys_jdisc_profile__categories,
            isys_jdisc_profile__import_all_software,
            isys_jdisc_profile__import_software_licences,
            isys_jdisc_profile__import_software_services,
            isys_jdisc_profile__import_all_networks,
            isys_jdisc_profile__import_all_clusters,
            isys_jdisc_profile__import_all_blade_connections,
            isys_jdisc_profile__jdisc_server,
            isys_jdisc_profile__import_custom_attributes,
            isys_jdisc_profile__import_all_vlans,
            isys_jdisc_profile__import_type_interfaces,
            isys_jdisc_profile__cmdb_status,
            isys_jdisc_profile__use_default_templates,
            isys_jdisc_profile__software_filter,
            isys_jdisc_profile__software_filter_type,
            isys_jdisc_profile__software_obj_title,
            isys_jdisc_profile__isys_obj_type__id__chassis_module,
            isys_jdisc_profile__update_objtype,
            isys_jdisc_profile__update_obj_title,
            isys_jdisc_profile__chassis_module_update_objtype,
            isys_jdisc_profile__use_simple_database_mod,
            isys_jdisc_profile__network_adresses,
            isys_jdisc_profile__import_type_dhcp_addresses,
            isys_jdisc_profile__software_filter_type_regexp,
            isys_jdisc_profile__import_cloud_subscriptions,
            isys_jdisc_profile__import_connection_endpoint,
            isys_jdisc_profile__import_create_cloud_users,
            isys_jdisc_profile__adopt_location,
            isys_jdisc_profile__last_scanned_device,
            isys_jdisc_profile__resolve_fqdn,
            isys_jdisc_profile__network_import_rules,
            isys_jdisc_profile__network_import_filter,
            isys_jdisc_profile__mac_filter_type,
            isys_jdisc_profile__mac_filter,
            isys_jdisc_profile__software_location_mode,
            isys_jdisc_profile__software_location_id,
            isys_jdisc_profile__network_location_mode,
            isys_jdisc_profile__network_location_id,
            isys_jdisc_profile__cmdb_status_created
            FROM isys_jdisc_profile WHERE TRUE";

        $query .= match (gettype($id)) {
            'int' => " AND isys_jdisc_profile__id = {$this->convert_sql_int($id)}",
            'array' => " AND isys_jdisc_profile__id {$this->prepare_in_condition([$id])} ",
            default => ''
        };

        return $this->retrieve($query . ';');
    }

    /**
     * @return array
     */
    public function getProperties(): array
    {
        return $this->getJdiscDao()->get_properties(isys_jdisc_dao::C__PROFILES);
    }

    /**
     * @return array
     */
    public function getObjectTypeAssignmentProperties(): array
    {
        return $this->getJdiscDao()->get_properties(isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS);
    }

    /**
    * @param int $id
    * @deprecated
    * @return mixed|null
    */
    public function getProfile(int $id)
    {
        $profile = $this->getJdiscDao()->get_profile($id);
        if (!isset($profile[$id])) {
            return null;
        }
        $data = $profile[$id];
        $data[isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS] = $this->getJdiscDao()->get_object_type_assignments_by_profile($id);

        return $data;
    }

    /**
     * @param array $ids
     *
     * @return bool
     */
    public function delete(array $ids): bool
    {
        try {
            $this->getJdiscDao()
                ->delete(isys_jdisc_dao::C__PROFILES, ['id' => $ids]);
            $this->getJdiscDao()
                ->delete(isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS, ['profile' => $ids]);

            return true;
        } catch (Throwable $e) {
            return false;
        }
    }

    /**
     * @param isys_jdisc_dao|null $jdiscDao
     * @return array
     */
    public function getJdiscTypes(?isys_jdisc_dao $jdiscDao = null): array
    {
        if ($jdiscDao === null) {
            $jdiscDao = $this->getJdiscDao();
        }
        $entities = $jdiscDao->get_jdisc_device_types();
        $jdiscType = [];

        foreach ($entities as $entity) {
            $jdiscType[$entity['id']] = $entity['singular'];
        }
        return $jdiscType;
    }

    /**
     * @param isys_jdisc_dao|null $jdiscDao
     * @return array
     */
    public function getJdiscOs(?isys_jdisc_dao $jdiscDao = null): array
    {
        if ($jdiscDao === null) {
            $jdiscDao = $this->getJdiscDao();
        }
        $entities = $jdiscDao->get_jdisc_operating_systems();
        $operatingSystems = [];
        foreach ($entities as $entity) {
            $value = $entity['osversion'];
            if (!empty($entity['osfamily'])) {
                $value .= ' (' . $entity['osfamily'] . ')';
            }
            $operatingSystems[$entity['id']] = $value;
        }
        return array_unique($operatingSystems);
    }

    /**
     * @param int $jdiscServer
     *
     * @return array
     */
    public function getJDiscGroups(int $jdiscServer): array
    {
        $servers = $this->getJdiscModule()->get_jdisc_servers_as_array();
        $data = [];

        if (empty($servers) || !isset($servers[$jdiscServer])) {
            return [];
        }

        try {
            $this->getJdiscModule()
                ->switch_database($jdiscServer);
            $groups = $this->getJdiscModule()
                ->get_jdisc_groups();
            $data = [];
            foreach ($groups as $group) {
                $data[$group['id']] = $group['name'];
            }
            return $data;
        } catch (Throwable) {
            return [];
        }
    }

    /**
     * @return bool
     */
    public function isJediVersion(): bool
    {
        return $this->getJdiscDao()->is_jedi_version();
    }

    /**
     * @return array
     * @throws \isys_exception_database
     */
    public function getObjectTypes(): array
    {
        $result = isys_cmdb_dao::instance(isys_application::instance()->container->get('database'))
            ->get_objtype(null, false, C__RECORD_STATUS__NORMAL);
        $language = isys_application::instance()->container->get('language');
        $objectTypes = [];

        while ($row = $result->get_row()) {
            $objectTypes[$row['isys_obj_type__id']] = $language->get($row['isys_obj_type__title']);
        }

        asort($objectTypes);

        return $objectTypes;
    }

    /**
     * @return array
     * @throws \isys_exception_database
     */
    public function getMatchingProfiles(): array
    {
        $result = MatchDao::instance($this->m_db)
            ->getMatchProfiles();

        $matchingProfiles = [];
        while ($row = $result->get_row()) {
            $matchingProfiles[$row['isys_obj_match__id']] = $row['isys_obj_match__title'];
        }
        asort($matchingProfiles);

        return $matchingProfiles;
    }

    /**
     * @param array|null $selectedCategories
     *
     * @return array
     */
    public function getSelectedCategories(?array $selectedCategories = []): array
    {
        $supportedCategories = $this->getJdiscDao()->get_supported_categories();
        foreach ($supportedCategories as $key => $supportedCategory) {
            if (empty($selectedCategories) || !in_array($supportedCategory['id'], $selectedCategories)) {
                $supportedCategories[$key]['sel'] = false;
            }
        }
        return $supportedCategories;
    }

    /**
     * @param array|null $selectedFilters
     * @return array
     */
    public function getNetworkAddressFilter(?array $selectedFilters = []): array
    {
        $addressFilters = isys_jdisc_dao::getNetworkAdressesFilter();
        foreach ($addressFilters as $key => $filter) {
            if (empty($selectedCategories) || !in_array($filter['id'], $selectedFilters)) {
                $addressFilters[$key]['sel'] = false;
            }
        }
        return $addressFilters;
    }

    /**
     * @return array
     */
    public function getTitleTransformation(): array
    {
        return [
            isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__AS_IS => 'LC__JDISC__TITLE_TRANSFORM__AS_IS',
            isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__UPPERCASE => 'LC__JDISC__TITLE_TRANSFORM__UPPERCASE',
            isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__LOWERCASE => 'LC__JDISC__TITLE_TRANSFORM__LOWERCASE'
        ];
    }

    /**
     * @return array
     */
    public function getMacCategoryIds(): array
    {
        $constants = [
            'C__CATG__NETWORK_PORT',
            'C__CATG__CONTROLLER_FC_PORT',
        ];

        return array_filter(array_map(fn ($const) => defined_or_default($const, null), $constants));
    }

    /**
     * @param array $post
     * @return array
     */
    public function mapObjectTypeAssignments(array $post): array
    {
        $data = $this->getJdiscDao()->transformDataByProperties(
            $this->getObjectTypeAssignmentProperties(),
            $post
        );
        $orderedData = [];
        $index = [];
        $counter = 0;
        if (isset($data['id'])) {
            foreach ($data['id'] as $dataValues) {
                foreach ($dataValues as $value) {
                    $orderedData[$value] = [];
                    $index[$counter++] = $value;
                }
            }
            $counter = 0;
        }

        foreach ($data as $key => $dataValues) {
            if (empty($index)) {
                $index = array_map(fn ($i) => $i, range(0, count($dataValues) - 1));
            }

            foreach ($dataValues as $value) {
                $entryId = $index[$counter++];
                $orderedData[$entryId][$key] = $value;
            }
            $counter = 0;
        }

        return $orderedData;
    }

    /**
     * @param int|null $id
     * @param array $data
     * @return array
     */
    public function save(?int $id, array $data)
    {
        // Profile:
        $properties = $this->getProperties();
        $saveData = [];

        foreach ($properties as $propertyKey => $propertyInfo) {
            if ($propertyKey === 'id' && ((isset($data['id']) && $data['id'] < 1) || !isset($data[$propertyKey]['id']))) {
                continue;
            }

            // Serialize categories:
            if ($propertyKey === 'categories' && is_array($data[$propertyKey])) {
                $data[$propertyKey] = serialize($data[$propertyKey]);
            }
            if ($propertyKey === 'network_adresses') {
                $data[$propertyKey] = serialize($data[$propertyKey]);
            }

            // format location properties
            $locationProperties = ['software_location', 'network_location'];
            if (in_array($propertyKey, $locationProperties) && is_array($data[$propertyKey]) && !empty($data[$propertyKey])) {
                // get first element of location array
                $data[$propertyKey] = $data[$propertyKey][0];
            }

            // Save property only if create and save are provided:
            if (array_key_exists(C__PROPERTY__PROVIDES, $propertyInfo)) {
                if ((isys_module_dao::C__PROPERTY__PROVIDES__CREATE & $propertyInfo[C__PROPERTY__PROVIDES]) ||
                    (isys_module_dao::C__PROPERTY__PROVIDES__SAVE & $propertyInfo[C__PROPERTY__PROVIDES])) {
                    $saveData[$propertyKey] = $data[$propertyKey];
                }
            }
        }

        if ($id !== null) {
            $saveData['id'] = $id;
        }

        if (($id = $this->getJdiscDao()->save(isys_jdisc_dao::C__PROFILES, $saveData))) {
            $this->getJdiscDao()->delete(isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS, ['profile' => $id]);
            foreach ($data[isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS] as $saveData) {
                $saveData['profile'] = $id;
                $this->getJdiscDao()->save(isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS, $saveData);
            }
            return true;
        }
        return false;
    }

    /**
     * @return false|string
     */
    public function getDefaultColumnSettings()
    {
        return JSON::encode([
            'jdisc-os' => 1,
            'object-title-transform' => 0,
            'fqdn-addition' => 0,
            'port-filter' => 0,
            'location' => 0,
        ]);
    }

    /**
     * @return array
     * @throws \idoit\Exception\JsonException
     */
    public function prepareData()
    {
        $data = $this->getJdiscDao()->transformDataByProperties($this->getProperties(), $_POST);
        $data[isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS] = $this->mapObjectTypeAssignments($_POST);

        return $data;
    }

    /**
     * @param array $data
     * @return int
     */
    public function saveProfile(array $data): int
    {
        $commonData  = $this->prepareCommonData();
        $categories  = $this->processCategories($commonData, $data);
        $profile     = $this->createProfileData($data, $categories);
        $assignments = $this->processTypeAssignments($data);
        $jdiscDao    = $this->getJdiscDao();

        if (!$profile['id']) {
            unset($profile['id']);
        }

        $id = $jdiscDao->save(isys_jdisc_dao::C__PROFILES, $profile);
        if ($id) {
            $jdiscDao->delete(isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS, ['profile' => $id]);
            foreach ($assignments as $assignment) {
                $assignment['profile'] = $id;
                $jdiscDao->save(isys_jdisc_dao::C__OBJECT_TYPE_ASSIGNMENTS, $assignment);
            }
        }

        return $id;
    }

    /**
     * @return array
     */
    private function prepareCommonData(): array
    {
        $jdisc = new isys_module_synetics_jdisc();
        return [
            'language'         => isys_application::instance()->container->get('language'),
            'statuses'         => $jdisc->callback_get_cmdb_status_as_array(new isys_request()),
            'types'            => $this->getObjectTypes(),
            'jdiscTypes'       => $this->getJdiscTypes(),
            'jdiscOs'          => $this->getJdiscOs(),
            'matchingProfiles' => $this->getMatchingProfiles(),
            'networkFilters'   => $this->getNetworkAddressFilter(),
            'allCategories'    => isys_cmdb_dao_jdisc::instance(
                isys_application::instance()->container->get('database')
            )->get_all_categories(),
        ];
    }

    /**
     * @param array $row
     * @param array $commonData
     * @return SyncProfile
     */
    private function processRow(array $row, array $commonData): SyncProfile
    {
        $data = [];
        foreach (self::PROFILE_FIELDS as $field) {
            $data[$field] = match ($field) {
                'categories' => $this->prepareCategories(
                    Unserialize::toArray($row[$field]),
                    $commonData['allCategories'],
                    $commonData['language']
                ),
                'jdisc_server' => $this->prepareServer(
                    $row[$field] ? (int) $row[$field] : null
                ),
                'mac_filter' => [
                    'type' => $row['mac_filter_type'] ? 'whitelist' : 'blacklist',
                    'value' => empty($row[$field]) ? null : $row[$field],
                ],
                'network_import_filter' => [
                    'type' => $row['network_import_rules'] ? 'whitelist' : 'blacklist',
                    'value' => empty($row[$field]) ? null : $row[$field],
                ],
                'software_filter' => [
                    'type' => $row['software_filter_type'] ? 'whitelist' : 'blacklist',
                    'as_regexp' => (int) $row['software_filter_type_regexp'],
                    'value' => empty($row[$field]) ? null : $row[$field],
                ],
                'cmdb_status', 'cmdb_status_create' => $this->prepareNamedValue(
                    $row[$field] ? (int) $row[$field] : null,
                    $commonData['statuses']
                ),
                'chassis_assigned_modules_objtype' => $this->prepareNamedValue(
                    $row[$field] ? (int) $row[$field] : null,
                    $commonData['types']
                ),
                'network_location' => $this->prepareLocation(
                    $row[$field] ? (int) $row[$field] : null,
                    (int) $row['network_location_mode']
                ),
                'software_location' => $this->prepareLocation(
                    $row[$field] ? (int) $row[$field] : null,
                    (int) $row['software_location_mode']
                ),
                'network_adresses' => $this->prepareNetworkAdresses(
                    Unserialize::toArray($row[$field]),
                    $commonData['networkFilters']
                ),
                'column_settings' => json_decode($row[$field], true),
                'conf_server' => $this->prepareServerConf(
                    $row[$field] ? (int) $row[$field] : null,
                    (int) $row['conf_group_id'],
                    $row['conf_filter'],
                    (int) $row['conf_import_mode'],
                    (int) $row['conf_logging'],
                    (int) $row['conf_overwrite_ip'],
                    (int) $row['conf_search_index'],
                ),
                default => $row[$field],
            };
        }

        foreach ($this->skippedFields as $field) {
            unset($data[$field]);
        }

        $profile = Serializer::fromJson(SyncProfile::class, $data);
        $profile->setTypeMap(
            $this->prepareTypeMap(
                (int) $row['id'],
                $commonData['jdiscTypes'],
                $commonData['types'],
                $commonData['jdiscOs'],
                $commonData['matchingProfiles']
            )
        );
        return $profile;
    }

    /**
     * @param array $allCategories
     * @param array $categories
     * @param isys_component_template_language_manager $language
     * @return array
     */
    private function prepareCategories(array $categories, array $allCategories, isys_component_template_language_manager $language): array
    {
        $preparedCategories = [];
        foreach ($categories as $category) {
            $preparedCategories[] = [
                'id'   => $allCategories[C__CMDB__CATEGORY__TYPE_GLOBAL][$category]['const'],
                'name' => $language->get($allCategories[C__CMDB__CATEGORY__TYPE_GLOBAL][$category]['title']),
            ];
        }
        return $preparedCategories;
    }

    /**
     * @param int|null $id
     * @return array|null
     * @todo cache server
     */
    private function prepareServer(?int $id): ?array
    {
        if ($id === null) {
            return null;
        }

        $server = isys_application::instance()
            ->container->get('idoit.jdisc.server.dao')
            ->getConfiguration($id);
        return [
            'id'   => $id,
            'name' => $server['title'] ?? null,
        ];
    }

    /**
     * @param int|null $id
     * @param array $options
     * @return array|null
     */
    private function prepareNamedValue(?int $id, array $options): ?array
    {
        if ($id === null) {
            return null;
        }
        return [
            'id'   => $id,
            'name' => $options[$id] ?? null,
        ];
    }

    /**
     * @param int|null $id
     * @param int|null $mode
     * @return array|null
     */
    private function prepareLocation(?int $id, ?int $mode): ?array
    {
        if ($id === null) {
            return null;
        }

        $name = isys_cmdb_dao::instance(isys_application::instance()->container->get('database'))
            ->get_obj_name_by_id_as_string($id);

        if ($mode === null) {
            return [
                'id'   => $id,
                'name' => $name,
            ];
        }

        return [
            'type'  => $mode,
            'value' => [
                'id'   => $id,
                'name' => $name,
            ],
        ];
    }

    /**
     * @param array $ids
     * @param array $networkAdresses
     * @return array|null
     */
    private function prepareNetworkAdresses(array $ids, array $networkAdresses): ?array
    {
        if (empty($ids)) {
            return null;
        }

        $prepareNetworkAdresses = [];
        foreach ($networkAdresses as $address) {
            if (in_array($address['id'], $ids)) {
                $prepareNetworkAdresses[] = [
                    'id'   => $address['id'],
                    'name' => $address['val'],
                ];
            }
        }
        return $prepareNetworkAdresses;
    }

    /**
     * @param int $profileId
     * @param array $jdiscTypes
     * @param array $types
     * @param array $jdiscOs
     * @param array $matchingProfiles
     * @return array
     */
    private function prepareTypeMap(int $profileId, array $jdiscTypes, array $types, array $jdiscOs, array $matchingProfiles): array
    {
        $assinments = $this->getJdiscDao()->get_object_type_assignments_by_profile($profileId);
        $typeMap = [];
        foreach ($assinments as $assignment) {
            $jdiscType = $assignment['jdisc_type_customized']
                ? [
                    'id'   => null,
                    'name' => $assignment['jdisc_type_customized'],
                ]
                : $this->prepareNamedValue($assignment['jdisc_type'] ? (int) $assignment['jdisc_type'] : null, $jdiscTypes);
            $os = $assignment['jdisc_os_customized']
                ? [
                    'id'   => null,
                    'name' => $assignment['jdisc_os_customized'],
                ]
                : $this->prepareNamedValue($assignment['jdisc_os'] ? (int) $assignment['jdisc_os'] : null, $jdiscOs);

            $portFilter = null;
            $portFilterType = json_decode($assignment['port_filter_type']) ?? [];
            $portFilterValue = json_decode($assignment['port_filter']) ?? [];
            foreach ($portFilterType as $key => $type) {
                $portFilter[] = [
                    'value' => $portFilterValue[$key],
                    'type'  => $type,
                ];
            }
            $typeMap[] = [
                'jdisc_type'      => $jdiscType,
                'idoit_type'      => $this->prepareNamedValue((int) $assignment['object_type'], $types),
                'jdisc_os'        => $os,
                'title_transform' => $this->prepareTitleTransform((int) $assignment['title_transform']),
                'fqdn_addition'   => $assignment['fqdn_addition'],
                'port_filter'     => $portFilter,
                'object_matching' => $this->prepareNamedValue((int) $assignment['matching_profile'], $matchingProfiles),
                'location'        => $this->prepareLocation($assignment['location'] ? (int) $assignment['location'] : null, null),
            ];
        }
        return $typeMap;
    }

    /**
     * @param int $titleTransforms
     * @return string
     */
    private function prepareTitleTransform(int $titleTransforms): string
    {
        return match ($titleTransforms) {
            isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__AS_IS     => 'as_is',
            isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__UPPERCASE => 'uppercase',
            isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__LOWERCASE => 'lowercase',
            default => 'as_is',
        };
    }

    /**
     * @param int|null $id
     * @param int|null $groupId
     * @param string|null $filter
     * @param int|null $importMode
     * @param int|null $logging
     * @param int|null $overwriteIp
     * @param int|null $searchIndex
     * @return array|null
     */
    private function prepareServerConf(?int $id, ?int $groupId, ?string $filter, ?int $importMode, ?int $logging, ?int $overwriteIp, ?int $searchIndex): ?array
    {
        if ($id === null) {
            return null;
        }

        $decodedFilter = $filter ? json_decode($filter, true) : null;

        return [
            'id'           => $id,
            'group_id'     => $groupId,
            'filter'       => $decodedFilter ?: ['type' => '', 'data' => $filter ?? ''],
            'import_mode'  => $importMode,
            'logging'      => $logging,
            'overwrite_ip' => $overwriteIp,
            'search_index' => $searchIndex,
        ];
    }

    /**
     * @param array $commonData
     * @param array $data
     * @return string
     */
    private function processCategories(array $commonData, array $data): string
    {
        $categoryConsts = array_column($data['categories'], 'id');

        $categories = [];
        foreach ($commonData['allCategories'][C__CMDB__CATEGORY__TYPE_GLOBAL] as $id => $category) {
            if (in_array($category['const'], $categoryConsts)) {
                $categories[] = $id;
            }
        }

        return serialize($categories);
    }

    /**
     * @param array $data
     * @param string $categories
     * @return array
     */
    private function createProfileData(array $data, string $categories): array
    {
        $profile = [
            'categories'                              => $categories,
            'chassis_assigned_modules_update_objtype' => $data['chassis_module_update_objtype'],
            'mac_filter_type'                         => $data['mac_filter']['type'] === 'whitelist' ? 1 : 0,
            'network_import_rules'                    => $data['network_import_filter']['type'] === 'whitelist' ? 1 : 0,
            'network_location_import_mode'            => $data['network_location']['type'],
            'software_filter_type'                    => $data['software_filter']['type'] === 'whitelist' ? 1 : 0,
            'software_filter_type_regexp'             => $data['software_filter']['as_regexp'],
            'software_location_import_mode'           => $data['software_location']['type'],
            'title'                                   => $data['name'],
            'column_settings'                         => json_encode($data['column_settings']),
            'conf_group_id'                           => $data['conf_server']['group_id'],
            'conf_filter'                             => isset($data['conf_server']['filter']) ? json_encode($data['conf_server']['filter']) : null,
            'conf_import_mode'                        => $data['conf_server']['import_mode'],
            'conf_logging'                            => $data['conf_server']['logging'],
            'conf_overwrite_ip'                       => $data['conf_server']['overwrite_ip'],
            'conf_search_index'                       => $data['conf_server']['search_index'],
        ];

        foreach (self::PROFILE_FIELDS as $field) {
            if (!isset($profile[$field])) {
                $profile[$field] = $this->getProfileFieldValue($field, $data);
            }
        }

        return $profile;
    }

    /**
     * @param string $field
     * @param array $data
     * @return mixed
     */
    private function getProfileFieldValue(string $field, array $data): mixed
    {
        return match ($field) {
            'jdisc_server', 'cmdb_status', 'cmdb_status_create', 'chassis_assigned_modules_objtype', 'conf_server' => $data[$field]['id'],
            'mac_filter', 'network_import_filter', 'software_filter' => $data[$field]['value'],
            'network_location', 'software_location' => $data[$field]['value']['id'],
            'network_adresses' => $data[$field] ? serialize(array_column($data[$field], 'id')) : null,
            default => $data[$field],
        };
    }

    /**
     * @param array $data
     * @return array
     */
    private function processTypeAssignments(array $data): array
    {
        $titleTransforms = [
            'as_is'     => isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__AS_IS,
            'uppercase' => isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__UPPERCASE,
            'lowercase' => isys_ajax_handler_jdisc::C__JDISC__TITLE_TRANSFORM__LOWERCASE,
        ];

        $assignments = [];
        foreach ($data['type_map'] as $assignment) {
            $assignments[] = [
                'jdisc_type'            => $assignment['jdisc_type']['id'],
                'jdisc_type_customized' => $assignment['jdisc_type']['id'] === null ? $assignment['jdisc_type']['name'] : null,
                'jdisc_os'              => $assignment['jdisc_os']['id'],
                'jdisc_os_customized'   => $assignment['jdisc_os']['id'] === null ? $assignment['jdisc_os']['name'] : null,
                'title_transform'       => $titleTransforms[$assignment['title_transform']],
                'fqdn_addition'         => $assignment['fqdn_addition'],
                'object_type'           => $assignment['idoit_type']['id'],
                'port_filter'           => $assignment['port_filter'] ? json_encode(array_column($assignment['port_filter'], 'value')) : null,
                'port_filter_type'      => $assignment['port_filter'] ? json_encode(array_column($assignment['port_filter'], 'type')) : null,
                'location'              => $assignment['location']['id'],
                'matching_profile'      => $assignment['object_matching']['id'],
            ];
        }

        return $assignments;
    }

    /**
     * @param int $serverId
     * @return array
     */
    public function getFormOptions(int $serverId): array
    {
        $jdisc = new isys_module_synetics_jdisc();
        $jdisc->switch_database($serverId);
        $jdiscDao = isys_cmdb_dao_jdisc::instance(isys_application::instance()->container->get('database'));
        $language = isys_application::instance()->container->get('language');

        return [
            'jdiscTypes'          => $this->getJdiscTypes($jdisc->m_dao),
            'jdiscOs'             => $this->getJdiscOs($jdisc->m_dao),
            'idoitTypes'          => $this->getObjectTypes(),
            'matchingProfiles'    => $this->getMatchingProfiles(),
            'locations'           => $this->getLocationTree(),
            'groups'              => $this->getGroups($jdisc),
            'filters'             => $this->getFilters($jdisc, $language),
            'statuses'            => $jdisc->callback_get_cmdb_status_as_array(new isys_request()),
            'categories'          => $this->getCategories($jdisc, $jdiscDao, $language),
            'switchTypeID'        => defined_or_default('C__OBJTYPE__SWITCH'),
            'counters'            => $jdisc->get_count_for_options(),
        ];
    }

    /**
     * @return array
     */
    public function getLocationTree(): array {
        $locationTree = [];
        $query = <<<SQL
            SELECT
                isys_obj__id,
                isys_obj__title,
                isys_catg_location_list__parentid
            FROM isys_catg_location_list
            INNER JOIN isys_obj ON isys_obj__id = isys_catg_location_list__isys_obj__id
            INNER JOIN isys_obj_type ON isys_obj_type__id = isys_obj__isys_obj_type__id
            WHERE
                isys_obj__status = {$this->convert_sql_int(C__RECORD_STATUS__NORMAL)}
                AND isys_obj_type__container = 1
            ORDER BY
                isys_catg_location_list__parentid,
                isys_obj__title
            SQL;
        $result = $this->retrieve($query);
        while ($row = $result->get_row()) {
            $locationTree[$row['isys_obj__id']] = [
                'title' => $row['isys_obj__title'],
                'parent' => $row['isys_catg_location_list__parentid'],
            ];
        }
        return $locationTree;
    }

    /**
     * @param isys_module_synetics_jdisc $jdisc
     * @return array
     */
    public function getGroups(isys_module_synetics_jdisc $jdisc): array {
        $groups = [];
        if (!$jdisc->is_jedi()) {
            $groupsData = $jdisc->get_jdisc_groups();
            if (count($groupsData) > 0) {
                foreach ($groupsData as $group) {
                    $groups[$group['id']] = $group['name'];
                }
                asort($groups);
            }
        }
        return $groups;
    }

    /**
     * @param isys_module_synetics_jdisc $jdisc
     * @param isys_component_template_language_manager $language
     * @return array
     */
    public function getFilters(isys_module_synetics_jdisc $jdisc, isys_component_template_language_manager $language): array {
        $filters = [];
        if (is_dir($filterDir = $jdisc::getPath() . 'templates/filter/')) {
            $filterList = scandir($filterDir);
            foreach ($filterList as $filterFile) {
                if ($filterFile === '.' || $filterFile === '..') {
                    continue;
                }

                if (file_exists($filterDir . $filterFile) && is_file($filterDir . $filterFile)) {
                    $filterFiles[] = $filterDir . $filterFile;
                    $filterType = rtrim($filterFile, '.tpl');
                    $filters[$filterType] = $language->get('LC__MODULE__JDISC__IMPORT__FILTER_TYPE__' . strtoupper($filterType));
                }
            }
        }
        return $filters;
    }

    /**
     * @param isys_module_synetics_jdisc $jdisc
     * @param isys_cmdb_dao_jdisc $jdiscDao
     * @param isys_component_template_language_manager $language
     * @return array
     */
    public function getCategories(isys_module_synetics_jdisc $jdisc, isys_cmdb_dao_jdisc $jdiscDao, isys_component_template_language_manager $language): array
    {
        $categoryList = $jdiscDao->get_all_categories();
        $supported = array_column($jdisc->m_dao->get_supported_categories(), 'id');
        $categories = [];
        foreach ($categoryList[C__CMDB__CATEGORY__TYPE_GLOBAL] as $category) {
            if (!in_array($category['id'], $supported)) {
                continue;
            }
            $categories[$category['const']] = $language->get($category['title']);
        }
        asort($categories);
        return $categories;
    }

    /**
     * @param array $ids
     * @return void
     */
    public function exportProfiles(array $ids): void
    {
        $result_set  = [];
        $jdiscModule = new isys_module_synetics_jdisc();
        $profileName = '';

        foreach ($ids as $id) {
            $profile     = $jdiscModule->load_profile((int)$id);
            $profileName = $profile['profiles']['title'] ?? '';

            $result_set[] = $profile;
        }

        // @see ID-9572 Use proper names for JDisc profile exports.
        $filename = count($ids) > 1
            ? 'jdisc-profiles'
            : "jdisc-profile_{$profileName}";

        $filename = \idoit\Component\Helper\Purify::formatFilename("{$filename}.json");

        isys_component_filemanager::sendInline(
            JSON::encode($result_set),
            $filename,
            'application/json'
        );
    }
}
