<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows\Automation\Action\PerformExecution;

use idoit\Module\SyneticsFlows\Automation\Action\ActionType\RankObjectAction;
use isys_event_manager;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionInfo\RankObjectInfo;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionResult\ErrorExecutionResult;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionResult\ExecutionResult;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionResult\RankObjectResult;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionResult\ValidationError;
use idoit\Module\SyneticsFlows\Automation\Action\ExecutionResult\ValidationErrorResult;
use idoit\Module\SyneticsFlows\Automation\Execution;
use isys_cmdb_dao;
use isys_exception_database;
use Throwable;

class RankObjectExecution implements PerformExecution
{
    private const NEXT_STATUSES = [
        C__RECORD_STATUS__NORMAL => C__RECORD_STATUS__ARCHIVED,
        C__RECORD_STATUS__ARCHIVED => C__RECORD_STATUS__DELETED,
    ];

    private const ALLOWED_STATUSES = [
        C__RECORD_STATUS__NORMAL,
        C__RECORD_STATUS__ARCHIVED,
        C__RECORD_STATUS__DELETED,
    ];

    private const LOG_MESSAGES = [
        C__RECORD_STATUS__NORMAL => 'C__LOGBOOK_EVENT__OBJECT_RECYCLED',
        C__RECORD_STATUS__ARCHIVED => 'C__LOGBOOK_EVENT__OBJECT_ARCHIVED',
        C__RECORD_STATUS__DELETED => 'C__LOGBOOK_EVENT__OBJECT_DELETED',
    ];

    /**
     * @param isys_cmdb_dao $cmdbDao
     */
    public function __construct(private isys_cmdb_dao $cmdbDao)
    {
    }

    /**
     * @param Execution $execution
     *
     * @return ExecutionResult
     */
    public function perform(Execution $execution): ExecutionResult
    {
        $parameters = $execution->getExecution();
        if (!$parameters instanceof RankObjectInfo) {
            return new ErrorExecutionResult('Unsupported execution');
        }

        try {
            $objectIds = $parameters->getObjectIds();

            $validationErrors = $this->validateStatus($parameters->getStatus());
            if (!empty($validationErrors)) {
                return new ValidationErrorResult($validationErrors);
            }

            foreach ($objectIds as $objectId) {
                $objectId = (int)$objectId;
                $newStatus = $this->getNextStatus($objectId, $parameters);
                if ($newStatus) {
                    $objectData = $this->cmdbDao->get_object($objectId)->get_row();
                    $this->cmdbDao->set_object_status($objectId, $newStatus);
                    isys_event_manager::getInstance()
                        ->triggerCMDBEvent(
                            self::LOG_MESSAGES[$newStatus],
                            '',
                            $objectId,
                            $objectData['isys_obj__isys_obj_type__id'],
                        );
                }
            }

            return new RankObjectResult($objectIds, $parameters->getStatus());
        } catch (Throwable $e) {
            return new ErrorExecutionResult("Something went wrong while ranking objects: {$e->getMessage()}");
        }
    }

    /**
     * @param string $status
     * @return array
     */
    private function validateStatus(string $status): array
    {
        $errors = [];

        if ($status !== RankObjectAction::STATUS_NEXT && !in_array((int)$status, self::ALLOWED_STATUSES)) {
            $errors[] = new ValidationError('C__CATG__GLOBAL', 'C__CATG__GLOBAL.status', 'Invalid status value');
        }

        return $errors;
    }

    /**
     * @param int $objectId
     * @param RankObjectInfo $parameters
     * @return int|null
     *
     * @throws isys_exception_database
     */
    private function getNextStatus(int $objectId, RankObjectInfo $parameters): ?int
    {
        if ($parameters->getStatus() === RankObjectAction::STATUS_NEXT) {
            $objectData = $this->cmdbDao->get_object($objectId)->get_row();
            if (!isset(self::NEXT_STATUSES[$objectData['isys_obj__status']])) {
                return null;
            }

            return self::NEXT_STATUSES[$objectData['isys_obj__status']];
        }

        return (int) $parameters->getStatus();
    }

    /**
     * @param Execution $execution
     *
     * @return bool
     */
    public function supports(Execution $execution): bool
    {
        return $execution->getExecution() instanceof RankObjectInfo;
    }
}
