<?php

namespace idoit\Module\Pro\Controller\CategoryFolders;

use Exception;
use idoit\Module\Cmdb\Component\Category\Category;
use idoit\Module\Cmdb\Component\Category\Item\AbstractItem;
use idoit\Module\Cmdb\Component\Category\Item\FolderItem;
use idoit\Module\Pro\Model\CategoryFolders\Category as CategoryModel;
use idoit\Module\Pro\Model\CategoryFolders\Config as ConfigModel;
use idoit\Module\Pro\Model\CategoryFolders\Folder as FolderModel;
use isys_application;
use isys_auth;
use isys_component_dao_result;
use isys_component_database;
use isys_component_template_language_manager;
use isys_exception_general;
use isys_format_json;
use isys_module_system as ModuleSystem;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

/**
 * Controller for 'category folders' configuration
 *
 * @package   Modules
 * @copyright synetics GmbH
 * @license   http://www.i-doit.com/license
 */
class Config
{
    private isys_component_database $database;
    private isys_component_template_language_manager $language;
    private ConfigModel $configModel;
    private FolderModel $folderModel;
    private CategoryModel $categoryModel;

    public function __construct()
    {
        $this->database = isys_application::instance()->container->get('database');
        $this->language = isys_application::instance()->container->get('language');
        $this->configModel = ConfigModel::instance($this->database);
        $this->folderModel = FolderModel::instance($this->database);
        $this->categoryModel = CategoryModel::instance($this->database);
    }

    /**
     * @param Request $request
     * @param string  $objectTypeId
     *
     * @return Response
     */
    public function loadByObjectType(Request $request, string $objectTypeId): Response
    {
        try {
            if (empty($objectTypeId)) {
                throw new isys_exception_general($this->language->get('LC__CATEGORY_FOLDERS__OBJECT_TYPE_VALIDATION'));
            }

            $result = [];
            $categoryComponent = new Category((int) $objectTypeId);
            $categories = $categoryComponent->getAssignedCategories();

            // Add a virtual 'root' entry for the legacy structure.
            if ($categoryComponent->isLegacy()) {
                $result[] = [
                    'id'         => 0,
                    'parent'     => null,
                    'name'       => $this->language->get('LC__CATEGORY_FOLDERS__POPUP__SELECT_FOLDER__ROOT_NO_PARENT'),
                    'rawName'    => 'LC__CATEGORY_FOLDERS__POPUP__SELECT_FOLDER__ROOT_NO_PARENT',
                    'type'       => null,
                    'isCategory' => false,
                    'isFolder'   => true
                ];
            }

            if ($categoryComponent->isLegacy()) {
                // @see ID-9963 Sort this by using 'strcmp' instead of 'strnatcasecmp'.
                usort($categories, fn (AbstractItem $a, AbstractItem $b) => strcasecmp(
                    isys_glob_replace_accent($a->getTranslatedName()),
                    isys_glob_replace_accent($b->getTranslatedName())
                ));
            }

            foreach ($categories as $category) {
                $result[] = [
                    'id'         => $category->getId(),
                    'parent'     => $categoryComponent->isLegacy() && $category->getParent() === null ? 0 : $category->getParent(),
                    'name'       => $category->getTranslatedName(),
                    'rawName'    => $category->getName(),
                    'type'       => $category::PREFIX,
                    'isCategory' => (!($category instanceof FolderItem)) && count($category->getChildren()) === 0,
                    'isFolder'   => $category instanceof FolderItem || count($category->getChildren()) > 0,
                    'rawId'      => $category->getRawId()
                ];
            }

            $response = [
                'success' => true,
                'data'    => [
                    'configId' => $categoryComponent->getConfigurationId(),
                    'categories' => $result
                ],
                'message' => ''
            ];
        } catch (Throwable $e) {
            $response = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return new JsonResponse($response);
    }

    /**
     * @param Request $request
     * @param string  $objectTypeId
     *
     * @return Response
     */
    public function saveForObjectType(Request $request, string $objectTypeId): Response
    {
        try {
            ModuleSystem::getAuth()->check(isys_auth::EDIT, 'category-folders');

            if (empty($objectTypeId)) {
                throw new isys_exception_general($this->language->get('LC__CATEGORY_FOLDERS__OBJECT_TYPE_VALIDATION'));
            }

            $parentMap = [];
            $folders = isys_format_json::decode($request->request->get('folders'));
            $categories = isys_format_json::decode($request->request->get('categories'));

            $configuration = $this->configModel->getByObjectType((int) $objectTypeId);

            if (is_array($configuration) && isset($configuration['id'])) {
                $configurationId = (int)$configuration['id'];
            } else {
                $configurationId = $this->configModel->create((int) $objectTypeId);
            }

            if (!is_int($configurationId)) {
                throw new isys_exception_general('The configuration could not be created!');
            }

            // First remove all current assignments.
            $this->categoryModel->deleteAllByConfig($configurationId);
            $this->folderModel->deleteAllByConfig($configurationId);

            // Then create folders.
            foreach ($folders as $folder) {
                $parent = $folder['parent'];

                if ($parent !== null) {
                    $parent = $parentMap[$parent];
                }

                $parentMap[$folder['id']] = $this->folderModel->create($parent, $configurationId, $folder['rawName'], $folder['order']);
            }

            // Finally create categories.
            foreach ($categories as $category) {
                $globalId = $category['type'] === 'g' ? $category['id'] : null;
                $customId = $category['type'] === 'c' ? $category['id'] : null;
                $specificId = $category['type'] === 's' ? $category['id'] : null;

                $this->categoryModel->create($parentMap[$category['parent']], $globalId, $customId, $specificId, $category['order']);
            }

            $response = [
                'success' => true,
                'data'    => null,
                'message' => ''
            ];
        } catch (Throwable $e) {
            $response = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return new JsonResponse($response);
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function editFolder(Request $request): Response
    {
        try {
            ModuleSystem::getAuth()->check(isys_auth::EDIT, 'category-folders');

            $folderId = (int)$request->request->get('folderId');
            $parentId = (int)$request->request->get('parentId');
            $configId = (int)$request->request->get('configId');
            $name = $this->validateFolderTitle($request->request->get('name'));

            if ($configId <= 0) {
                throw new isys_exception_general($this->language->get('LC__CATEGORY_FOLDERS__CONFIGURATION_VALIDATION'));
            }

            if ($parentId === 0) {
                // Find the root node:
                $root = $this->folderModel->getRootByConfig($configId);

                if (!is_array($root)) {
                    throw new isys_exception_general('The selected configuration does not exist!');
                }

                $parentId = $root['id'];
            } else {
                $parent = $this->folderModel->getById($parentId);

                if (!is_array($parent)) {
                    throw new isys_exception_general('The selected parent does not exist!');
                }

                if ($parent['config'] != $configId) {
                    throw new isys_exception_general('The selected parent does not exist within the given configuration!');
                }
            }

            if ($folderId > 0) {
                $this->folderModel->updateFolder($folderId, $parentId, $name);
            } elseif ($configId) {
                $this->folderModel->create($parentId, $configId, $name);
            }

            $response = [
                'success' => true,
                'data'    => null,
                'message' => ''
            ];
        } catch (Throwable $e) {
            $response = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return new JsonResponse($response);
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function deleteFolder(Request $request): Response
    {
        try {
            ModuleSystem::getAuth()->check(isys_auth::EDIT, 'category-folders');

            $configurationId = (int)$request->request->get('configurationId');
            $folderId = (int)$request->request->get('folderId');

            $folderData = $this->folderModel->getById($folderId);

            if (!is_array($folderData)) {
                throw new Exception('The folder does not exist!');
            }

            if ($folderData['config'] != $configurationId) {
                throw new Exception('The folder does not exist in this configuration!');
            }

            // @see ID-9538 Move assigned categories and folders.
            $childFolderResult = $this->folderModel->getAllByParent($folderId);
            $childCategoryResult = $this->categoryModel->getAllByFolder($folderId);

            if (count($childFolderResult) || count($childCategoryResult)) {
                $newParentFolderId = (int)$request->request->get('newParentId');

                if (!$newParentFolderId) {
                    throw new Exception('You need to select a new parent folder!');
                }

                $this->moveChildren($folderId, $newParentFolderId, $childFolderResult, $childCategoryResult);
            }

            // Finally delete the folder.
            $response = [
                'success' => $this->folderModel->deleteById($folderId),
                'data'    => null,
                'message' => ''
            ];
        } catch (Throwable $e) {
            $response = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return new JsonResponse($response);
    }

    /**
     * @param int                       $folderId
     * @param int                       $newParentFolderId
     * @param isys_component_dao_result $folderResult
     * @param isys_component_dao_result $categoryResult
     *
     * @return void
     * @throws \isys_exception_database
     */
    private function moveChildren(int $folderId, int $newParentFolderId, isys_component_dao_result $folderResult, isys_component_dao_result $categoryResult): void
    {
        if ($newParentFolderId === $folderId) {
            throw new Exception('You can not move the items here!');
        }

        if (!$this->folderModel->getById($newParentFolderId)) {
            throw new Exception('New parent folder could not be found!');
        }

        while ($folder = $folderResult->get_row()) {
            $this->folderModel->updateFolder((int)$folder['id'], $newParentFolderId);
        }

        while ($category = $categoryResult->get_row()) {
            $this->categoryModel->updateCategory((int)$category['id'], $newParentFolderId);
        }
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function moveItem(Request $request): Response
    {
        try {
            ModuleSystem::getAuth()->check(isys_auth::EDIT, 'category-folders');

            $configurationId = (int)$request->request->get('configurationId');
            $categoryId = (int)$request->request->get('categoryId');
            $folderId = (int)$request->request->get('folderId');
            $parentId = (int)$request->request->get('parentId');
            $newItem = (bool)$request->request->get('newItem');
            $newItemType = $request->request->get('newItemType');

            $folderData = $this->folderModel->getById($parentId);

            if (!is_array($folderData)) {
                throw new Exception('The folder does not exist!');
            }

            if ($folderData['config'] != $configurationId) {
                throw new Exception('The folder does not exist in this configuration!');
            }

            if ($categoryId > 0) {
                if ($newItem && in_array($newItemType, ['g', 's', 'c'], true)) {
                    $this->categoryModel->create(
                        $parentId,
                        $newItemType === 'g' ? $categoryId : null,
                        $newItemType === 'c' ? $categoryId : null,
                        $newItemType === 's' ? $categoryId : null,
                        $folderData['order']++
                    );
                } else {
                    $this->categoryModel->updateCategory($categoryId, $parentId, $folderData['order']++);
                }
            }

            if ($folderId > 0) {
                $this->folderModel->updateFolder($folderId, $parentId, null, $folderData['order']++);
            }

            // Finally delete the folder.
            $response = [
                'success' => true, // $this->folderModel->deleteById($folderId),
                'data'    => null,
                'message' => ''
            ];
        } catch (Throwable $e) {
            $response = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return new JsonResponse($response);
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function reset(Request $request): Response
    {
        try {
            ModuleSystem::getAuth()->check(isys_auth::EDIT, 'category-folders');

            $configurationId = (int)$request->request->get('configurationId');

            // Finally delete the folder.
            $response = [
                'success' => $this->configModel->delete($configurationId),
                'data'    => null,
                'message' => ''
            ];
        } catch (Throwable $e) {
            $response = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return new JsonResponse($response);
    }

    /**
     * @param string $title
     *
     * @return string
     * @throws isys_exception_general
     */
    private function validateFolderTitle(string $title): string
    {
        $title = trim($title);

        if ($title === '') {
            throw new isys_exception_general($this->language->get('LC__CATEGORY_FOLDERS__FOLDER_NAME_VALIDATION'));
        }

        if (mb_strlen($title) < 3) {
            throw new isys_exception_general($this->language->get('LC__CATEGORY_FOLDERS__FOLDER_NAME_VALIDATION_MINLENGTH'));
        }

        return $title;
    }
}
