<?php

namespace idoit\Module\SyneticsFlows\Controller;

use idoit\Module\SyneticsFlows\Auth;
use idoit\Module\SyneticsFlows\Automation\Automation;
use idoit\Module\SyneticsFlows\Automation\Trigger\Invocation\InvocationContext;
use idoit\Module\SyneticsFlows\Automation\Trigger\TriggerType\ScheduleTriggerType;
use idoit\Module\SyneticsFlows\Controller\Response\ArrayResponse;
use idoit\Module\SyneticsFlows\Controller\Response\IdResponse;
use idoit\Module\SyneticsFlows\Controller\Response\MalformedResponse;
use idoit\Module\SyneticsFlows\Controller\Response\NotFoundResponse;
use idoit\Module\SyneticsFlows\Export\FlowsExportService;
use idoit\Module\SyneticsFlows\Export\Formatter\JsonFormatter;
use idoit\Module\SyneticsFlows\Model\AutomationDao;
use idoit\Module\SyneticsFlows\Model\ScheduleDao;
use idoit\Module\SyneticsFlows\PermissionService;
use idoit\Module\SyneticsFlows\Serialization\Serializer;
use idoit\Module\SyneticsFlows\Validation\Validation;
use isys_auth;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Trigger controller
 *
 * @package   idoit\Module\synetics_flows\Controller
 * @copyright synetics
 * @license   https://www.i-doit.com/
 */
class AutomationController extends BaseController
{
    private ?AutomationDao $automationDao = null;

    public function find(Request $request): Response
    {
        if (!Auth::instance()->manageFlows(isys_auth::VIEW)) {
            return JSONResponseFactory::accessDenied();
        }

        $search = SearchParams::fromParams($request->get('params'));
        return new ArrayResponse(
            $this->getDao()->getData(null, $search),
            $this->getDao()->getCount(),
        );
    }

    public function get(string $id): Response
    {
        if (!Auth::instance()->manageFlows(isys_auth::VIEW)) {
            return JSONResponseFactory::accessDenied();
        }

        $result = $this->getDao()->get($id);
        if ($result === null) {
            return new NotFoundResponse('Automation');
        }
        return new JsonResponse($result);
    }

    public function create(Request $request): Response
    {
        if (!Auth::instance()->manageFlows(isys_auth::CREATE)) {
            return JSONResponseFactory::accessDenied();
        }

        $array = $this->extractData($request);
        $automation = Serializer::fromJson(Automation::class, $array);

        if (!PermissionService::hasAccess($automation)) {
            return JSONResponseFactory::accessDenied('Limited access');
        }

        $errors = Validation::validate($automation);
        if (!empty($errors)) {
            return new MalformedResponse($this->translateErrorMessages($errors));
        }

        $context = InvocationContext::fromEnvironment();
        $id = $this->getDao()->save($automation, (int)$context->getUserId(), $context->getTime());

        return new IdResponse($id);
    }

    public function update(string $id, Request $request): Response
    {
        if (!Auth::instance()->manageFlows(isys_auth::EDIT)) {
            return JSONResponseFactory::accessDenied();
        }

        $automation = $this->getDao()->get($id);
        $array = $this->extractData($request);

        $oldData = $automation->jsonSerialize();
        $automation = Serializer::fromJson(Automation::class, array_merge($automation->jsonSerialize(), $array));

        if (!PermissionService::hasAccess($automation)) {
            return JSONResponseFactory::accessDenied('Limited access');
        }

        $errors = Validation::validate($automation);

        if (!empty($errors)) {
            return new MalformedResponse($this->translateErrorMessages($errors));
        }
        $context = InvocationContext::fromEnvironment();
        $id = $this->getDao()->save($automation, (int)$context->getUserId(), $context->getTime());

        if ($automation instanceof Automation
            && $automation->getTrigger() instanceof ScheduleTriggerType
            && $this->isAutomationDataHasDifference($oldData, $array)
        ) {
            $this->getScheduleDao()->cleanUpUpcoming((int)$automation->getId());
        }

        return new IdResponse($id);
    }

    /**
     * @param array $oldData
     * @param array $newData
     *
     * @return bool
     */
    private function isAutomationDataHasDifference(array $oldData, array $newData): bool
    {
        foreach (['trigger', 'condition', 'actions'] as $key) {
            if (json_encode($oldData[$key]) !== json_encode($newData[$key])) {
                return true;
            }
        }
        return false;
    }

    public function remove(string $id): Response
    {
        if (!Auth::instance()->manageFlows(isys_auth::DELETE)) {
            return JSONResponseFactory::accessDenied();
        }

        $automation = $this->getDao()->get($id);
        if (!$automation) {
            throw new NotFoundHttpException('Automation not found');
        }
        $this->getDao()->remove($id);

        return new IdResponse($id);
    }

    public function validate(Request $request): Response
    {
        if (!Auth::instance()->hasRight(isys_auth::CREATE) && !Auth::instance()->hasRight(isys_auth::EDIT)) {
            return JSONResponseFactory::accessDenied();
        }

        $array = $this->extractData($request);
        $data = Serializer::fromJson($request->attributes->get('_class'), $array);
        $errors = Validation::validate($data);

        return new MalformedResponse($this->translateErrorMessages($errors));
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function export(Request $request): Response
    {
        $params = $this->extractData($request);
        if (empty($params['ids'])) {
            return new JsonResponse([
                'type' => 'error',
                'error' => 'Not provided flows ids for export',
            ], Response::HTTP_BAD_REQUEST);
        }
        $data = $this->getDao()->find($params['ids']);
        if (!count($data)) {
            return new NotFoundResponse('Automation');
        }
        $result = $this->getExportService()->export(JsonFormatter::FORMAT, $data);

        return new Response($result, 200, ['Content-Type' => JsonFormatter::MIME_TYPE]);
    }

    /**
     * @return FlowsExportService
     *
     * @throws \Exception
     */
    private function getExportService(): FlowsExportService
    {
        return \isys_application::instance()->container->get('idoit.flows.export.service');
    }

    public function getDao(): AutomationDao
    {
        if (!$this->automationDao) {
            $this->automationDao = \isys_application::instance()->container->get('idoit.flows.automation.dao');
        }
        return $this->automationDao;
    }

    /**
     * @return ScheduleDao
     */
    public function getScheduleDao(): ScheduleDao
    {
        return \isys_application::instance()->container->get('idoit.flows.schedule.dao');
    }
}
