<?php

namespace idoit\Module\Forms\Model;

use idoit\Component\Property\Property;
use idoit\Model\Dao\Base;
use idoit\Module\Forms\Blocked;
use idoit\Module\Forms\Exceptions\CategoryException;
use idoit\Module\Forms\Model\CategoryTypes\GlobalCategories;
use idoit\Module\Forms\Model\CategoryTypes\SpecificCategories;
use idoit\Module\Forms\Model\CategoryTypes\CustomCategories;
use idoit\Module\Forms\Model\CategoryTypes\Virtual\VirtualCategory;
use idoit\Module\Forms\Model\CategoryTypes\VirtualCategories;
use idoit\Module\Multiedit\Component\Filter\CategoryFilter;
use idoit\Module\Multiedit\Model\Categories;
use isys_application;
use isys_cmdb_dao_category;
use isys_cmdb_dao_category_g_custom_fields;
use isys_component_dao_result;

class CategoryDao extends Base
{
    /**
     * @param Categories $categories
     *
     * @return array
     * @throws \isys_exception_database
     */
    private function extractCategories(Categories $categories)
    {
        $return = [];

        if ($categories->getData()) {
            $multiValueList = $categories->getMultivalueCategories();
            $database = isys_application::instance()->container->get('database');

            foreach ($categories->getData() as $categoryCombinedKey => $categoryTitle) {
                [$categoryKey, $categoryClass] = explode(':', $categoryCombinedKey);
                [$categoryType, $categoryId] = explode('_', $categoryKey);

                if (!class_exists($categoryClass)) {
                    continue;
                }

                $categoryObject = $categoryClass::instance($database);
                $isVirtual = in_array(VirtualCategory::class, class_implements($categoryObject), true);

                if (!$categoryObject instanceof isys_cmdb_dao_category) {
                    continue;
                }

                $isMultiValue = (bool)$multiValueList[$categoryKey];
                $const = $categoryObject instanceof VirtualCategory ? $categoryObject::getVirtualCategoryConst() : $categoryObject->get_category_const();

                if ($categoryObject instanceof isys_cmdb_dao_category_g_custom_fields) {
                    $categoryObject->set_catg_custom_id($categoryId)
                        ->setCategoryInfo($categoryId);
                    $const = $categoryObject->get_catg_custom_const();
                }

                if ($this->hasProperties($const) || $isVirtual) {
                    $return[] = [
                        'id'                    => $categoryId,
                        'const'                 => $const,
                        'title'                 => $categoryTitle,
                        'multivalue'            => $isMultiValue,
                        'className'             => $categoryClass,
                        'requiredAttributes'    => $this->getMandatoryProperties($categoryObject),
                        'parentChildAttributes' => $this->getParentChildProperties($categoryObject)
                    ];
                }
            }
        }

        return $return;
    }

    /**
     * @param array $classes
     *
     * @return array
     * @throws \idoit\Module\Multiedit\Component\Multiedit\Exception\CategoryDataException
     * @throws \isys_exception_database
     */
    public function getByClasses(array $classes): array
    {
        $categoryFilter = new CategoryFilter();
        $categoryFilter->setObjectTypes($classes);

        $database = \isys_application::instance()->container->get('database');
        /**
         * Categories
         */
        $globalCategories = GlobalCategories::instance($database)
            ->setFilter($categoryFilter)
            ->setData();
        $globalCategories = $this->extractCategories($globalCategories);

        $specificCategories = SpecificCategories::instance($database)
            ->setFilter($categoryFilter)
            ->setData();
        $specificCategories = $this->extractCategories($specificCategories);

        $customCategories = CustomCategories::instance($database)
            ->setFilter($categoryFilter)
            ->setData();
        $customCategories = $this->extractCategories($customCategories);

        $virtualCategories = VirtualCategories::instance($database)
            ->setFilter($categoryFilter)
            ->setData();
        $virtualCategories = $this->extractCategories($virtualCategories);

        return array_merge($globalCategories, $specificCategories, $customCategories, $virtualCategories);
    }

    /**
     * Skip categories which have no attributes which are supported
     *
     * @param $categoryConstant
     *
     * @return bool
     * @throws \isys_exception_database
     */
    public function hasProperties($categoryConstant)
    {
        $checkSql = 'SELECT isys_property_2_cat__prop_key FROM isys_property_2_cat
                    WHERE isys_property_2_cat__cat_const = ' . $this->convert_sql_text($categoryConstant) . '
                        AND isys_property_2_cat__prop_type = ' . $this->convert_sql_int(C__PROPERTY_TYPE__STATIC) . '
                        AND isys_property_2_cat__prop_key != ' . $this->convert_sql_text('description') . '
                        AND isys_property_2_cat__prop_provides & ' .
            $this->convert_sql_int(C__PROPERTY__PROVIDES__MULTIEDIT);

        $checkResult = $this->retrieve($checkSql);

        if (count($checkResult) === 0) {
            return false;
        }

        return true;
    }

    /**
     * @param string $categoryConstant
     *
     * @throws CategoryException
     */
    public function loadCategoryDaoByConstant(string $categoryConstant)
    {
        $escapedCategoryConst = $this->convert_sql_text($categoryConstant);
        $query = "SELECT isysgui_catg__id AS id, isysgui_catg__class_name AS categoryClass FROM isysgui_catg
            WHERE isysgui_catg__const = {$escapedCategoryConst}
            UNION
            SELECT isysgui_cats__id AS id, isysgui_cats__class_name AS categoryClass FROM isysgui_cats
            WHERE isysgui_cats__const = {$escapedCategoryConst}
            UNION
            SELECT isysgui_catg_custom__id as id, 'isys_cmdb_dao_category_g_custom_fields' AS categoryClass FROM
            isysgui_catg_custom WHERE isysgui_catg_custom__const = {$escapedCategoryConst}";

        $result = $this->retrieve($query);
        $categoryData = $result->get_row();
        $categoryClass = $categoryData['categoryClass'];

        // @see ID2-3225 In this case we'll first try to find virtual categories before we throw an error.
        if (!class_exists($categoryClass)) {
            $categoryFilter = new CategoryFilter();
            $categoryFilter->setCategories([$categoryConstant]);

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

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

                if ($categoryConst === $categoryConstant) {
                    $categoryClass = $categoryDaoClass;
                }
            }
        }

        if (!class_exists($categoryClass)) {
            throw CategoryException::CategoryClassIsUnknown($categoryConstant);
        }

        /**
         * @var isys_cmdb_dao_category $dao
         */
        $dao = $categoryClass::instance(isys_application::instance()->container->get('database'));

        if ($dao instanceof isys_cmdb_dao_category_g_custom_fields) {
            $dao->set_catg_custom_id($categoryData['id']);
        }

        return $dao;
    }

    /**
     * @param isys_cmdb_dao_category $dao
     *
     * @return array
     */
    private function getMandatoryProperties(isys_cmdb_dao_category $dao)
    {
        $properties = $dao->get_properties(C__PROPERTY__WITH__VALIDATION);
        $mandatoryProperties = array_filter($properties, function ($item) {
            return $item[Property::C__PROPERTY__CHECK][Property::C__PROPERTY__CHECK__MANDATORY];
        });

        return $mandatoryProperties;
    }

    /**
     * @param isys_cmdb_dao_category $dao
     *
     * @return array
     */
    public function getParentChildProperties(isys_cmdb_dao_category $dao)
    {
        $properties = $dao->get_properties();
        $result = [];
        foreach ($properties as $firstKey => $property) {
            $children = [];
            foreach ($properties as $secondKey => $secondProperty) {
                $dependantPropKey = $secondProperty[Property::C__PROPERTY__DEPENDENCY][Property::C__PROPERTY__DEPENDENCY__PROPKEY]
                    ?? null;
                if ($dependantPropKey !== null && $firstKey === $dependantPropKey) {
                    $children[$secondKey] = $secondProperty;
                    continue;
                }
            }

            if (!empty($children)) {
                $result[] = [
                    'parent'   => [
                        $firstKey => $property
                    ],
                    'children' => $children
                ];
            }
        }

        return $result;
    }
}
