<?php

namespace idoit\Module\Forms\Controller;

use Exception;
use idoit;
use idoit\Component\ClassLoader\ModuleLoader;
use idoit\Component\Property\Property;
use idoit\Controller\Base;
use idoit\Module\Cmdb\Component\AttributeDataCollector\Collector;
use idoit\Module\Cmdb\Component\AttributeDataCollector\CollectorTypes\BrowserObject;
use idoit\Module\Cmdb\Interfaces\ObjectBrowserReceiver;
use idoit\Module\Cmdb\Model\Tree;
use idoit\Module\Forms\Blocked;
use idoit\Module\Forms\Exceptions\DataByAttributeException;
use idoit\Module\Forms\Model\AttributeDao;
use idoit\Module\Forms\Model\AttributeDefaultValueDao;
use idoit\Module\Forms\Model\CategoryDao;
use idoit\Module\Forms\Model\ClassDao;
use isys_application as Application;
use isys_cmdb_dao;
use isys_cmdb_dao_category;
use isys_cmdb_dao_category_g_custom_fields;
use isys_component_database_proxy;
use isys_component_template_language_manager;
use isys_component_tree;
use isys_controller as Controller;
use isys_module;
use isys_register as Register;

class DataApi extends Base implements Controller
{
    /**
   * @var Collector
   */
  private $attributeDataCollector = null;

  /**
   * @var isys_component_database_proxy
   */
  private $database;

  /**
   * @var isys_component_template_language_manager
   */
  private $language;

  /**
   * @var string|array|null
   */
  protected $response = null;

  /**
   * @var string
   */
  private $wwwPath;

  public function pre(): void
  {
    header('Content-Type: application/json; charset=UTF-8');
  }

  public function post(): void
  {
    echo json_encode($this->response);
    exit;
  }

  /**
   * @param isys_module $p_module
   *
   * @throws \Exception
   */
  public function __construct(isys_module $p_module)
  {
    $this->database = $this->getDi()->get('database');
    $this->language = $this->getDi()->get('language');
    $this->wwwPath = Application::instance()->www_path;
    $this->attributeDataCollector = new Collector();
  }

  public function dao(Application $application)
  {
    // Nothing to do here.
  }

  public function handle(Register $request, Application $application)
  {
    // Nothing to do here.
  }

  public function tree(Register $request, Application $application, isys_component_tree $tree)
  {
    // Nothing to do here.
  }

  /**
   * @param Register $register
   * @param ModuleLoader $moduleLoader
   *
   * @return void
   */
  public function getLocationTree(Register $register, ModuleLoader $moduleLoader): void
  {
    $tree = new Tree($this->database);
    $this->response = $tree->getLocationChildren(C__OBJ__ROOT_LOCATION, Tree::MODE_PHSYICAL, true, 1000, true);
  }

  /**
   * @param Register     $request
   * @param ModuleLoader $moduleLoader
   *
   * @throws \Exception
   */
  public function getClasses(Register $request, ModuleLoader $moduleLoader): void
  {
    $this->response = [];
    $result = ClassDao::instance($this->database)->getAll();
    $routeGenerator = \isys_application::instance()->container->get('route_generator');

    while ($row = $result->get_row()) {
      $icon = $routeGenerator->generate('cmdb.object-type.icon', ['objectTypeId' => (int)$row['type_id']]);

      $this->response[] = [
        'id'    => $row['id'],
        'title' => $this->language->get($row['title']),
        'icon'  => $icon,
      ];
    }
  }

  /**
   * @param Register     $request
   * @param ModuleLoader $moduleLoader
   *
   * @throws \isys_exception_database
   */
    public function getCategories(Register $request, ModuleLoader $moduleLoader): void
    {
        $this->response = [];
        $classes = array_filter(explode(',', $request->get('GET')->get('class')));

        if (!count($classes)) {
            $this->response['error'] = 'You need to provide at least one class!';

            return;
        }

        $categoryDao = CategoryDao::instance($this->database);
        $attributeDao = AttributeDao::instance($this->database);

        foreach ($categoryDao->getByClasses($classes) as $row) {
            if (in_array($row['const'], Blocked::CATEGORIES, true)) {
                continue;
            }

            $parentChildAttributes = $requiredAttributes = [];
            $class = $row['className'];

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

            $daoInstance = $class::instance($this->database);

            if (!empty($row['parentChildAttributes'])) {
                $parentChildAttributes = $attributeDao->buildParentChildAttributes($row['const'], $row['parentChildAttributes'], $daoInstance);
            }

            if (!empty($row['requiredAttributes'])) {
                $requiredAttributes = $attributeDao->buildRequiredAttributes($row['const'], $row['requiredAttributes'], $daoInstance);
            }

            $this->response[] = [
                'id'                    => $row['const'],
                'title'                 => $this->language->get($row['title']),
                'multivalue'            => (bool)$row['multivalue'],
                'requiredAttributes'    => $requiredAttributes,
                'parentChildAttributes' => $parentChildAttributes
            ];
        }
    }

  /**
   * @param Register     $request
   * @param ModuleLoader $moduleLoader
   *
   * @throws \isys_exception_database
   */
  public function getAttributes(Register $request, ModuleLoader $moduleLoader): void
  {
    $this->response = [];

    $categories = array_filter(explode(',', $request->get('GET')->get('category')));
    $objectType = $request->get('GET')->get('class') ?? null;

    if (is_string($objectType)) {
      $objectType = isys_cmdb_dao::instance($this->database)->getObjectTypeId($objectType);
    }

    if (!count($categories)) {
      $this->response['error'] = 'You need to provide at least one category!';

      return;
    }

    $attributeDao = AttributeDao::instance($this->database);
    $categoryDao = CategoryDao::instance($this->database);
    $defaultValueDao = AttributeDefaultValueDao::factory($this->database, $objectType);

    $result = $attributeDao->getByCategory($categories);

    foreach ($result as $row) {
      /** @var isys_cmdb_dao_category $daoName */
      $daoName = $row['className'];

      if (!class_exists($daoName) || in_array($row['const'], Blocked::CATEGORIES, true)) {
        // Skip blocked categories.
        continue;
      }

      $daoInstance = $daoName::instance($this->database);

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

      $properties = $daoInstance->get_properties(C__PROPERTY__WITH__VALIDATION);
      $parentChildProperties = $categoryDao->getParentChildProperties($daoInstance);

      foreach ($properties as $key => $property) {
        if (in_array($row['const'] . '.' . $key, Blocked::ATTRIBUTES, true)) {
          // Skip blocked properties.
          continue;
        }

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

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

        $childAttributes = $attributeDao->getDependantAttribute($row['const'], $key, $parentChildProperties, $daoInstance, $attributeDao::PARENT_ATTRIBUTE);
        $parentAttribute = $attributeDao->getDependantAttribute($row['const'], $key, $parentChildProperties, $daoInstance, $attributeDao::CHILD_ATTRIBUTES);

        $this->response[] = [
          'id'    => "{$row['const']}.{$key}",
          'title' => $this->language->get($property[C__PROPERTY__INFO][C__PROPERTY__INFO__TITLE]),
          'type'  => $attributeType,
          'isSystemRequired' => (bool) $property[C__PROPERTY__CHECK][C__PROPERTY__CHECK__MANDATORY],
          'defaultValue' => $defaultValueDao->getDefaultPropertyValue($row['const'], $key),
          $attributeDao::PARENT_ATTRIBUTE => $parentAttribute,
          $attributeDao::CHILD_ATTRIBUTES => $childAttributes
        ];
      }
    }
  }

  /**
   * @param Register     $request
   * @param ModuleLoader $moduleLoader
   *
   * @throws \isys_exception_database
   */
  public function getDataByAttribute(Register $request, ModuleLoader $moduleLoader): void
  {
    $this->response = [];

    $attribute = $request->get('attributeId');

    try {
      if ($attribute === null) {
        throw DataByAttributeException::NoAttributeInRequest();
      }

      [$categoryConst, $propertyKey] = explode('.', $attribute);

      if (!$categoryConst) {
        throw DataByAttributeException::NoCategoryConstantDefined();
      }

      if (in_array($categoryConst, Blocked::CATEGORIES, true)) {
        throw DataByAttributeException::CategoryIsBlocked($categoryConst);
      }

      if (!$propertyKey) {
        throw DataByAttributeException::NoPropertyKeyDefined();
      }

      if (in_array($attribute, Blocked::ATTRIBUTES, true)) {
        throw DataByAttributeException::CategoryPropertyIsBlocked($categoryConst, $propertyKey);
      }

      $attributeDao = AttributeDao::instance($this->database);

      $categoryData = $attributeDao->getByCategory([$categoryConst]);

      if (count($categoryData)) {
          $categoryData = $categoryData[0];
      }

      if (!class_exists($categoryData['className'])) {
        throw DataByAttributeException::CategoryDaoDoesNotExist($categoryConst);
      }

      $daoInstance = $categoryData['className']::instance($this->database);

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

      $properties = $daoInstance->get_properties();

      foreach ($properties as $key => $property) {

        if ($key !== $propertyKey) {
          continue;
        }

        if (!$property instanceof Property) {
          $property = Property::createInstanceFromArray($property);
        }

        if (!$this->attributeDataCollector->isApplicable($property)) {
          throw DataByAttributeException::NoCollectorFound($propertyKey, $categoryConst);
        }

        try {
            $dataCollectorType = $this->attributeDataCollector->getApplicableType();

            if ($dataCollectorType instanceof BrowserObject) {
                // @see AOF-5 Do not show the category name of attributes.
                if (method_exists($dataCollectorType, 'displayAttributeCategories')) {
                    $dataCollectorType->displayAttributeCategories(false);
                }

                // @see AOF-3 Add default attributes.
                if (method_exists($dataCollectorType, 'overwriteAttributeConfiguration')) {
                    $dataCollectorType->overwriteAttributeConfiguration([
                        'C__CATG__GLOBAL::title',
                        'C__CATG__GLOBAL::type',
                        'C__CATG__GLOBAL::sysid'
                    ]);
                }
            }

          $this->response = $dataCollectorType->collectData($property, true);

          return;
        } catch (Exception $e) {
          throw DataByAttributeException::FailedToCollectData($key, $e->getMessage());
        }
      }

      throw DataByAttributeException::PropertyDoesNotExistInCategory($propertyKey, $categoryConst);
    } catch (Exception $e) {
      $this->response['error'] = $e->getMessage();

      return;
    }
  }
}
