<?php

namespace idoit\Module\Pro\Model;

use Exception;
use idoit\Component\Property\Property;
use idoit\Model\Dao\Base;
use isys_application;
use isys_cmdb_dao_category;
use isys_cmdb_dao_category_g_custom_fields;
use isys_cmdb_dao_category_global;
use isys_component_dao_result;
use isys_format_json;

class AttributeSettings extends Base
{
    private const FIELDS = [
        'isys_property_config__id'                      => 'id',
        'isys_property_config__isys_obj_type__id'       => 'objectTypeId',
        'isys_property_config__property_reference'      => 'propertyReference',
        'isys_property_config__isysgui_catg__id'        => 'catG',
        'isys_property_config__isysgui_catg_custom__id' => 'catC',
        'isys_property_config__isysgui_cats__id'        => 'catS',
        'isys_property_config__ui_id'                   => 'uiId',
        'isys_property_config__visible'                 => 'visible',
        'isys_property_config__visible_overview'        => 'visibleOverview',
        'isys_property_config__mandatory'               => 'mandatory',
        'isys_property_config__unique_object'           => 'uniqueObject',
        'isys_property_config__unique_object_type'      => 'uniqueObjectType',
        'isys_property_config__unique_global'           => 'uniqueGlobal',
        'isys_property_config__validation'              => 'validation',
        'isys_property_config__validation_option'       => 'validationOption'
    ];

    const UNHIDEABLE_FIELDS = [
        'C__CATG__GLOBAL::title',
        'C__CATG__GLOBAL::status',
        'C__CATG__GLOBAL::cmdb_status',
        'C__CATG__GLOBAL::created_by',
        'C__CATG__GLOBAL::changed_by'
    ];

    const CONTACT_CATEGORIES = [
        'C__CATS__PERSON_MASTER',
        'C__CATS__PERSON_GROUP_MASTER',
        'C__CATS__ORGANIZATION_MASTER_DATA',
        'C__CATS__PERSON',
        'C__CATS__PERSON_GROUP',
        'C__CATS__ORGANIZATION',
    ];

    /**
     * @param string $constant
     * @param array  $setting
     *
     * @return array
     */
    private static function getContactDescriptions(string $constant, array $setting): array
    {
        $personFn = function ($setting) {
            $id = 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__PERSON', '');
            if ($setting['id'] === 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__PERSON', '')) {
                $id = 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__PERSON_MASTER', '');
            }

            $setting['uId'] = $setting['id'] = $id;

            return $setting;
        };

        $personGroupFn = function ($setting) {
            $id = 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__PERSON_GROUP', '');
            if ($setting['id'] === 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__PERSON_GROUP', '')) {
                $id = 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__PERSON_GROUP_MASTER', '');
            }

            $setting['uId'] = $setting['id'] = $id;

            return $setting;
        };

        $organisationFn = function ($setting) {
            $id = 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__ORGANIZATION', '');
            if ($setting['id'] === 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__ORGANIZATION', '')) {
                $id = 'C__CMDB__CAT__COMMENTARY_' . C__CMDB__CATEGORY__TYPE_SPECIFIC . defined_or_default('C__CATS__ORGANIZATION_MASTER_DATA', '');
            }

            $setting['uId'] = $setting['id'] = $id;

            return $setting;
        };

        $categoryConstantFnMap = [
            'C__CATS__PERSON'                   => $personFn,
            'C__CATS__PERSON_MASTER'            => $personFn,
            'C__CATS__ORGANIZATION'             => $organisationFn,
            'C__CATS__ORGANIZATION_MASTER_DATA' => $organisationFn,
            'C__CATS__PERSON_GROUP'             => $personGroupFn,
            'C__CATS__PERSON_GROUP_MASTER'      => $personGroupFn,
        ];

        return $categoryConstantFnMap[$constant]($setting);
    }

    /**
     * @param string $categoryConstant
     *
     * @return array
     * @throws \isys_exception_database
     */
    public static function getAttributeVisibilityForCategories(string $categoryConstant): array
    {
        $settings = [];
        $dao = self::instance(isys_application::instance()->container->get('database'));

        if ($categoryConstant === 'C__CATG__OVERVIEW') {
            $result = $dao->getAll();
        } else {
            $result = $dao->getByCategoryConstant($categoryConstant);
        }

        while ($row = $result->get_row()) {
            [$currentCategoryConstant, $propertyKey] = explode('::', $row['propertyReference']);

            // The attribute should be visible, skip the setting.
            if ($row['visible'] && $row['visibleOverview']) {
                continue;
            }

            if ($categoryConstant !== 'C__CATG__OVERVIEW' && !$row['visibleOverview']) {
                continue;
            }

            $settings[] = [
                'categoryConstant' => $currentCategoryConstant,
                'propertyKey'      => $propertyKey,
                'guiId'            => $row['uiId']
            ];
        }

        if (count($settings)) {
            $alteredSettings = $settings;

            foreach ($settings as $setting) {
                if ($setting['propertyKey'] === 'description' && in_array($setting['categoryConstant'], self::CONTACT_CATEGORIES)) {
                    $alteredSettings[] = self::getContactDescriptions($setting['categoryConstant'], $setting);
                }
            }

            $settings = $alteredSettings;
        }

        return array_map(fn ($item) => $item['guiId'], $settings);
    }

    /**
     * @return isys_component_dao_result
     * @throws \isys_exception_database
     */
    public function getAll(): isys_component_dao_result
    {
        $fields = $this->selectImplode(self::FIELDS);

        return $this->retrieve("SELECT {$fields} FROM isys_property_config;");
    }

    /**
     * @param int $id
     *
     * @return array|null
     * @throws \isys_exception_database
     */
    public function getById(int $id): ?array
    {
        $fields = $this->selectImplode(self::FIELDS);

        return $this->retrieve("SELECT {$fields} FROM isys_property_config WHERE isys_property_config__id = {$id} LIMIT 1;")
            ->get_row();
    }

    /**
     * @param int   $id
     * @param array $configuration
     *
     * @return bool
     * @throws \isys_exception_dao
     */
    public function edit(int $id, array $configuration): bool
    {
        $fieldValue = [];

        if (isset($configuration['visible'])) {
            $fieldValue[] = 'isys_property_config__visible = ' . $this->convert_sql_boolean($configuration['visible']);
        }

        if (isset($configuration['visibleOverview'])) {
            $fieldValue[] = 'isys_property_config__visible_overview = ' . $this->convert_sql_boolean($configuration['visibleOverview']);
        }

        if (isset($configuration['mandatory'])) {
            $fieldValue[] = 'isys_property_config__mandatory = ' . $this->convert_sql_boolean($configuration['mandatory']);
        }

        if (isset($configuration['uniqueObject'])) {
            $fieldValue[] = 'isys_property_config__unique_object = ' . $this->convert_sql_boolean($configuration['uniqueObject']);
        }

        if (isset($configuration['uniqueObjectType'])) {
            $fieldValue[] = 'isys_property_config__unique_object_type = ' . $this->convert_sql_boolean($configuration['uniqueObjectType']);
        }

        if (isset($configuration['uniqueGlobal'])) {
            $fieldValue[] = 'isys_property_config__unique_global = ' . $this->convert_sql_boolean($configuration['uniqueGlobal']);
        }

        if (isset($configuration['validation'])) {
            if ($configuration['validation'] == -1) {
                $fieldValue[] = 'isys_property_config__validation = NULL';
            } else {
                $fieldValue[] = 'isys_property_config__validation = ' . $this->convert_sql_text($configuration['validation']);
            }
        }

        if (isset($configuration['validationOption'])) {
            if (is_array($configuration['validationOption'])) {
                $fieldValue[] = 'isys_property_config__validation_option = ' . $this->convert_sql_text(isys_format_json::encode($configuration['validationOption']));
            } else {
                $fieldValue[] = 'isys_property_config__validation_option = NULL';
            }
        }

        if (count($fieldValue) === 0) {
            return true;
        }

        $fieldValues = implode(', ', $fieldValue);

        $sql = "UPDATE isys_property_config SET {$fieldValues}
            WHERE isys_property_config__id = {$id}
            LIMIT 1;";

        return $this->update($sql) && $this->apply_update();
    }

    /**
     * @param array $ids
     *
     * @return bool
     * @throws \isys_exception_dao
     */
    public function delete(array $ids): bool
    {
        $ids = $this->prepare_in_condition(array_filter($ids, 'is_int'));

        return $this->update("DELETE FROM isys_property_config WHERE isys_property_config__id {$ids};") && $this->apply_update();
    }

    /**
     * @param string $categoryConstant
     *
     * @return isys_component_dao_result
     * @throws \isys_exception_database
     */
    public function getByCategoryConstant(string $categoryConstant): isys_component_dao_result
    {
        $fields = $this->selectImplode(self::FIELDS);
        $referenceCondition = $this->convert_sql_text("{$categoryConstant}::%");

        return $this->retrieve("SELECT {$fields} FROM isys_property_config WHERE isys_property_config__property_reference LIKE {$referenceCondition}");
    }

    /**
     * @param string $propertyReference
     *
     * @return void
     * @throws \isys_exception_dao
     * @throws \isys_exception_database
     */
    public function createSettingWithDefaults(string $propertyReference): void
    {
        [$categoryConstant, $key] = explode('::', $propertyReference);

        $propertyReferenceValue = $this->convert_sql_text($propertyReference);

        $dao = $this->getCategoryDaoByConstant($categoryConstant);
        $categoryId = $this->convert_sql_id($dao->get_category_id());

        $property = $dao->get_property_by_key($key);

        if (!$property) {
            throw new Exception('The property ' . $key . ' could not be found in category ' . $categoryConstant);
        }

        $uiId = $this->convert_sql_text($property[C__PROPERTY__UI][C__PROPERTY__UI__ID]);

        if ($dao instanceof isys_cmdb_dao_category_g_custom_fields) {
            $categoryId = $this->convert_sql_id($dao->get_catg_custom_id());
            $field = "isys_property_config__isysgui_catg_custom__id";

            if ($key !== 'description') {
                $uiId = $this->convert_sql_text('C__CATG__CUSTOM__' . $property[C__PROPERTY__UI][C__PROPERTY__UI__ID]);
            }
        } elseif ($dao instanceof isys_cmdb_dao_category_global) {
            $field = "isys_property_config__isysgui_catg__id";
        } else {
            $field = "isys_property_config__isysgui_cats__id";
        }

        $fieldsData = [
            "isys_property_config__property_reference = {$propertyReferenceValue}",
            "{$field} = {$categoryId}",
            "isys_property_config__ui_id = {$uiId}"
        ];

        $sql = "INSERT INTO isys_property_config SET " . implode(', ', $fieldsData) . ';';

        $this->update($sql) && $this->apply_update();
    }

    /**
     * @param string $constant
     *
     * @return isys_cmdb_dao_category
     * @throws \isys_exception_database
     */
    public function getCategoryDaoByConstant(string $constant): isys_cmdb_dao_category
    {
        $constantValue = $this->convert_sql_text($constant);

        $sql = "SELECT isysgui_catg__id AS id, isysgui_catg__class_name AS className
            FROM isysgui_catg
            WHERE isysgui_catg__const = {$constantValue}

            UNION

            SELECT isysgui_cats__id AS id, isysgui_cats__class_name AS className
            FROM isysgui_cats
            WHERE isysgui_cats__const = {$constantValue}

            UNION

            SELECT isysgui_catg_custom__id AS id, isysgui_catg_custom__class_name AS className
            FROM isysgui_catg_custom
            WHERE isysgui_catg_custom__const = {$constantValue};";

        $row = $this->retrieve($sql)->get_row();

        if ($row === null) {
            throw new Exception("The category '{$constant}' could not be found. Was the category removed?");
        }

        if (!class_exists($row['className'])) {
            throw new Exception('The DAO class ' . $row['className'] . ' does not exist.');
        }

        if (!is_a($row['className'], isys_cmdb_dao_category::class, true)) {
            throw new Exception('The DAO class ' . $row['className'] . ' needs to extend isys_cmdb_dao_category.');
        }

        $dao = $row['className']::instance($this->m_db);

        if ($row['className'] === 'isys_cmdb_dao_category_g_custom_fields') {
            /** @var isys_cmdb_dao_category_g_custom_fields $dao */
            $dao->set_catg_custom_id($row['id']);
        }

        return $dao;
    }

    /**
     * @param string   $propertyReference
     * @param Property $property
     *
     * @return array
     */
    public function getPropertyOptions(string $propertyReference, Property $property): array
    {
        return [
            'isUnhideable'   => in_array($propertyReference, self::UNHIDEABLE_FIELDS, true),
            'isReadonly'     => $property->getData()
                ->isReadOnly(),
            'isVirtual'      => $property->getProvides()
                ->isVirtual(),
            'canBeValidated' => $property->getProvides()
                ->isValidation(),
        ];
    }
}
