<?php

namespace idoit\Module\Forms\Model;

use idoit\Component\Property\Property;
use idoit\Model\Dao\Base;
use idoit\Module\Cmdb\Interfaces\ObjectBrowserReceiver;
use idoit\Module\Forms\Model\CategoryTypes\VirtualCategories;
use idoit\Module\Multiedit\Component\Filter\CategoryFilter;
use isys_application;
use isys_cmdb_dao_category;
use isys_component_dao_result;

class AttributeDao extends Base
{
    /**
     * @var array
     */
    private static $categoryTypeInstance = [];

    public const PARENT_ATTRIBUTE = 'parent';

    public const CHILD_ATTRIBUTES = 'children';

    public const OBJECT_BROWSER_TYPE = 'object_browser';

    public const OBJECT_BROWSER_MULTI_TYPE = 'object_browser_multi';

    public const LOCATION_BROWSER_TYPE = 'location_browser';

    public const AVAILABLE_ATTRIBUTE_TYPES = [
        Property::C__PROPERTY__INFO__TYPE__TEXT,
        Property::C__PROPERTY__INFO__TYPE__TEXTAREA,
        Property::C__PROPERTY__INFO__TYPE__DOUBLE,
        Property::C__PROPERTY__INFO__TYPE__FLOAT,
        Property::C__PROPERTY__INFO__TYPE__INT,
        Property::C__PROPERTY__INFO__TYPE__MONEY,
        Property::C__PROPERTY__INFO__TYPE__DIALOG,
        Property::C__PROPERTY__INFO__TYPE__DIALOG_PLUS,
        Property::C__PROPERTY__INFO__TYPE__DIALOG_LIST,
        Property::C__PROPERTY__INFO__TYPE__MULTISELECT,
        Property::C__PROPERTY__INFO__TYPE__DATE,
        Property::C__PROPERTY__INFO__TYPE__DATETIME,
        Property::C__PROPERTY__INFO__TYPE__COMMENTARY,
        Property::C__PROPERTY__INFO__TYPE__PASSWORD,
        self::OBJECT_BROWSER_TYPE,
        self::OBJECT_BROWSER_MULTI_TYPE,
        self::LOCATION_BROWSER_TYPE
    ];

    /**
     * @param array $categories
     *
     * @return array
     * @throws \isys_exception_database
     */
    public function getByCategory(array $categories): array
    {
        $categoryData = [];
        $recordStatusNormal = $this->convert_sql_int(C__RECORD_STATUS__NORMAL);
        $categoryConstants = implode(',', array_map(function ($class) {
            return $this->convert_sql_text($class);
        }, $categories));

        $sql = "SELECT
              isysgui_catg__id AS id,
              isysgui_catg__class_name AS className,
              isysgui_catg__const AS const,
              '" . C__CMDB__CATEGORY__TYPE_GLOBAL . "' as type
            FROM isysgui_catg
            WHERE isysgui_catg__const IN ({$categoryConstants})
            AND isysgui_catg__status = {$recordStatusNormal}

            UNION

            SELECT
              isysgui_catg_custom__id AS id,
              'isys_cmdb_dao_category_g_custom_fields' AS className,
              isysgui_catg_custom__const AS const,
              '" . C__CMDB__CATEGORY__TYPE_CUSTOM . "' as type
            FROM isysgui_catg_custom
            WHERE isysgui_catg_custom__const IN ({$categoryConstants})
            AND isysgui_catg_custom__status = {$recordStatusNormal}

            UNION

            SELECT
              isysgui_cats__id AS id,
              isysgui_cats__class_name AS className,
              isysgui_cats__const AS const,
              '" . C__CMDB__CATEGORY__TYPE_SPECIFIC . "' as type
            FROM isysgui_cats
            WHERE isysgui_cats__const IN ({$categoryConstants})
            AND isysgui_cats__status = {$recordStatusNormal};";

        $result = $this->retrieve($sql);

        while ($row = $result->get_row()) {
            $categoryData[] = $row;
        }

        $categoryFilter = new CategoryFilter();
        $categoryFilter->setCategories($categories);

        $virtualCategories = VirtualCategories::instance($this->m_db)
            ->setFilter($categoryFilter)
            ->setData()
            ->getData();

        foreach ($virtualCategories as $categoryCombinedKey => $categoryTitle) {
            [$categoryConstant, $categoryClass] = explode(':', $categoryCombinedKey);

            $categoryData[] = [
                'id' => 0,
                'className' => $categoryClass,
                'const' => $categoryConstant,
                'type' => null,
            ];
        }

        return $categoryData;
    }

    /**
     * @param array  $property
     * @param string $key
     *
     * @return bool
     */
    public static function isSupportable(array $property, string $key): bool
    {
        if ($key === 'description') {
            return true;
        }

        // This check needs to be there because of the conversion of Property from object to array
        if (isset($property[C__PROPERTY__PROVIDES]['virtual']) &&
            isset($property[C__PROPERTY__PROVIDES]['multiedit'])) {
            if ($property[C__PROPERTY__PROVIDES]['virtual'] || !$property[C__PROPERTY__PROVIDES]['multiedit']) {
                return false;
            }

            return true;
        }

        if ($property[C__PROPERTY__PROVIDES][C__PROPERTY__PROVIDES__VIRTUAL] ||
            !$property[C__PROPERTY__PROVIDES][C__PROPERTY__PROVIDES__MULTIEDIT]) {
            return false;
        }

        return true;
    }

    /**
     * @param string                 $propertyKey
     * @param array                  $property
     * @param isys_cmdb_dao_category $categoryDao
     *
     * @return string|null
     */
    public function getAttributeType(string $propertyKey, array $property, isys_cmdb_dao_category $categoryDao): ?string
    {
        $attributeType = $this->getPropertyType($property, $categoryDao);

        if (!in_array($attributeType, self::AVAILABLE_ATTRIBUTE_TYPES, true)) {
            // Skip properties that are not available.
            return null;
        }

        if (!self::isSupportable($property, $propertyKey)) {
            // Skip virtual properties.
            return null;
        }

        return $attributeType;
    }

    /**
     * @param string                 $parentPropertyKey
     * @param string                 $categoryConstant
     * @param array                  $children
     * @param isys_cmdb_dao_category $categoryDao
     *
     * @return array
     * @throws \ReflectionException
     */
    private function buildDependantChilds(
        string $parentPropertyKey,
        string $categoryConstant,
        array $children,
        isys_cmdb_dao_category $categoryDao
    ) {
        $return = [];
        $language = isys_application::instance()->container->get('language');

        foreach ($children as $propKey => $property) {
            if ($property instanceof Property) {
                $property = $property->toArray();
            }

            if (($attributeType = $this->getAttributeType($propKey, $property, $categoryDao)) === null) {
                continue;
            }

            $return[] = [
                'id'                   => "{$categoryConstant}.{$propKey}",
                'title'                => $language->get($property[C__PROPERTY__INFO][C__PROPERTY__INFO__TITLE]),
                'type'                 => $attributeType,
                'isSystemRequired'     => (bool)$property[C__PROPERTY__CHECK][C__PROPERTY__CHECK__MANDATORY],
                self::PARENT_ATTRIBUTE => "{$categoryConstant}.{$parentPropertyKey}"
            ];
        }

        return $return;
    }

    /**
     * @param string                 $categoryConstant
     * @param array                  $data
     * @param isys_cmdb_dao_category $categoryDao
     *
     * @return array
     * @throws \ReflectionException
     *
     */
    public function buildParentChildAttributes(
        string $categoryConstant,
        array $data,
        isys_cmdb_dao_category $categoryDao
    ): array {
        $parentChildAttributes = [];
        $language = isys_application::instance()->container->get('language');

        foreach ($data as $dependantAttributes) {
            $parentPropertyData = $dependantAttributes[self::PARENT_ATTRIBUTE];
            $parentPropertyKey = key($parentPropertyData);
            $parentProperty = current($parentPropertyData);

            if ($parentProperty instanceof Property) {
                $parentProperty = $parentProperty->toArray();
            }

            if (($attributeType = $this->getAttributeType($parentPropertyKey, $parentProperty, $categoryDao)) ===
                null) {
                continue;
            }

            $children = $this->buildDependantChilds($parentPropertyKey, $categoryConstant,
                $dependantAttributes[self::CHILD_ATTRIBUTES], $categoryDao);

            if (empty($children)) {
                continue;
            }

            $parentChildAttributes[] = [
                'id'                   => "{$categoryConstant}.{$parentPropertyKey}",
                'title'                => $language->get($parentProperty[C__PROPERTY__INFO][C__PROPERTY__INFO__TITLE]),
                'type'                 => $attributeType,
                'isSystemRequired'     => (bool)$parentProperty[C__PROPERTY__CHECK][C__PROPERTY__CHECK__MANDATORY],
                self::CHILD_ATTRIBUTES => $children
            ];
        }

        return $parentChildAttributes;
    }

    /**
     * @param string                 $categoryConst
     * @param array                  $data
     * @param isys_cmdb_dao_category $daoInstance
     *
     * @return array
     *
     * @throws \ReflectionException
     */
    public function buildRequiredAttributes(
        string $categoryConst,
        array $data,
        isys_cmdb_dao_category $daoInstance
    ): array {
        $requiredAttribues = [];
        $language = isys_application::instance()->container->get('language');

        foreach ($data as $propertyKey => $property) {
            if ($property instanceof Property) {
                $property = $property->toArray();
            }

            if (($attributeType = $this->getAttributeType($propertyKey, $property, $daoInstance)) === null) {
                continue;
            }

            $requiredAttribues[] = [
                'id'               => "{$categoryConst}.{$propertyKey}",
                'title'            => $language->get($property[C__PROPERTY__INFO][C__PROPERTY__INFO__TITLE]),
                'type'             => $attributeType,
                'isSystemRequired' => (bool)$property[C__PROPERTY__CHECK][C__PROPERTY__CHECK__MANDATORY]
            ];
        }

        return $requiredAttribues;
    }

    /**
     * @param string                 $categoryConstant
     * @param string                 $propertyKey
     * @param array                  $parentChildProperties
     * @param isys_cmdb_dao_category $categoryDao
     *
     * @return array
     * @throws \ReflectionException
     */
    public function getDependantAttribute(
        string $categoryConstant,
        string $propertyKey,
        array $parentChildProperties,
        isys_cmdb_dao_category $categoryDao,
        string $direction = self::CHILD_ATTRIBUTES
    ): ?array {
        $data = [];
        $parentChildProperty = array_filter($parentChildProperties, function ($item) use ($propertyKey, $direction) {
            return isset($item[$direction][$propertyKey]);
        });

        if (!empty($parentChildProperty)) {
            $parentChildData = $this->buildParentChildAttributes($categoryConstant, $parentChildProperty, $categoryDao);
            switch ($direction) {
                case self::PARENT_ATTRIBUTE:
                    $data = current($parentChildData)[self::CHILD_ATTRIBUTES];
                    break;
                case self::CHILD_ATTRIBUTES:
                default:
                    $data = current($parentChildData);
                    unset($data[self::CHILD_ATTRIBUTES]);
                    break;
            }
        }

        return is_array($data) && !empty($data) ? $data : null;
    }

    /**
     * @param array                  $property
     * @param isys_cmdb_dao_category $daoInstance
     *
     * @return string
     */
    private function getPropertyType(array $property, isys_cmdb_dao_category $daoInstance): string
    {
        $type = $property[C__PROPERTY__INFO][C__PROPERTY__INFO__TYPE];
        $uiType = $property[C__PROPERTY__UI][C__PROPERTY__UI__TYPE] ?: '';
        $uiParams = $property[C__PROPERTY__UI][C__PROPERTY__UI__PARAMS] ?: [];

        if ($type === Property::C__PROPERTY__INFO__TYPE__TEXT && $uiType === C__PROPERTY__UI__TYPE__PASSWORD) {
            return Property::C__PROPERTY__INFO__TYPE__PASSWORD;
        }

        if ($type === Property::C__PROPERTY__INFO__TYPE__N2M) {
            return AttributeDao::OBJECT_BROWSER_MULTI_TYPE;
        }

        if ($type === Property::C__PROPERTY__INFO__TYPE__OBJECT_BROWSER) {
            switch ($uiParams['p_strPopupType']) {
                case 'browser_object_ng':

                    if (isset($uiParams['multiselection']) && $uiParams['multiselection']) {
                        return AttributeDao::OBJECT_BROWSER_MULTI_TYPE;
                    }

                    return AttributeDao::OBJECT_BROWSER_TYPE;
                case 'browser_location':
                    return AttributeDao::LOCATION_BROWSER_TYPE;

                case 'cable_connection_ng':
                    return 'connector_browser';

                case 'fc_port':
                    return 'fc_port_broser';

                case 'fc_port_san_zoning':
                    return 'san_zoning_browser';

                case 'file':
                    return 'file_browser';

                case 'object_relation':
                    return 'relation_browser';

                case 'sanpool':
                    return 'sanpool_browser';
            }
        }

        return (string)$type;
    }
}
