<?php

namespace Velis\Mvc\Controller;

use Exception;
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Dispatcher;
use ReflectionException;
use Throwable;
use Velis\Api\HttpExceptionHandler\ExceptionHandler;
use Velis\Dto\Common\Result\PaginatedListResult;
use Velis\Dto\Common\Result\Result;
use Velis\App;
use Velis\Dto\BaseDto;
use Velis\Dto\Exceptions\ValidationException;
use Velis\Exception\TokenExpiredException;
use Velis\Exception\UnauthorizedException;
use Velis\Filter;
use Velis\Http\Response\ZipResponseTrait;
use Velis\Lang;
use Velis\Mvc\Controller\Exception\AbstractHttpException;
use Velis\Mvc\Controller\Exception\BadRequestException;
use Velis\Mvc\Controller\Exception\ForbiddenException;
use Velis\Mvc\Controller\Exception\FormValidationException;
use Velis\Mvc\Controller\RestRequestHandler\AuthenticationMiddleware;
use Velis\Mvc\Controller\RestRequestHandler\ValidateDtoRequest;
use Velis\Mvc\Controller\RestRequestHandler\ValidateFormParams;
use Velis\Mvc\Controller\RestRequestHandler\VerifyCsrfToken;
use Velis\Mvc\Paginator;

/**
 * The Rest API for frontends
 *
 * @author Konrad Choma <konrad.choma@velistech.com>
 * @deprecated Use \Velis\Mvc\Controller\AbstractRestController instead
 */
abstract class RestController extends Controller
{
    use ZipResponseTrait;

    protected Filter $params;
    protected Paginator $paginator;

    public function onConstruct(): void
    {
        $this->view->disable();
        $this->params = App::$registry['filter'];
    }

    public function checkPriv(string $module, string $priv): void
    {
        if (!App::$user->hasPriv($module, $priv)) {
            throw new ForbiddenException(Lang::get('GENERAL_PERM_DENIED'));
        }
    }

    private function setActionParams(Dispatcher $dispatcher): void
    {
        $this->params = new Filter(
            array_merge(
                $this->params->getRawCopy(),
                $this->request->getJsonRawBody(true) ?: [],
                $dispatcher->getParams(),
            ),
            App::$config->settings->autoFiltering
        );
    }

    /**
     * @throws FormValidationException
     * @throws ReflectionException
     * @throws ForbiddenException
     * @throws UnauthorizedException
     * @throws TokenExpiredException
     */
    public function beforeExecuteRoute(Dispatcher $dispatcher): bool
    {
        $this->setActionParams($dispatcher);

        $checkAuth = new AuthenticationMiddleware();
        $verifyCsrfToken = new VerifyCsrfToken();
        $transformDtoRequest = new ValidateDtoRequest($dispatcher);
        $validateFormParams = new ValidateFormParams($this->params, $dispatcher);

        $checkAuth
            ->setNext($verifyCsrfToken)
            ->setNext($transformDtoRequest)
            ->setNext($validateFormParams);

        $checkAuth->handle($this->request);

        $this->paginator = Paginator::fromParams($this->params);

        return true;

        // TODO : check OPTIONS and ACCEPT headers
    }

    public function afterExecuteRoute(Dispatcher $dispatcher): void
    {
        if (self::isPrintAction($dispatcher->getActionName())) {
            $this->view->enable();
            $this->view->setViewsDir(MODULE_PATH . $this->dispatcher->getModuleName() . '/templates');
            $this->view->setBlank();
            return;
        }

        $data = $dispatcher->getReturnedValue();

        if ($data instanceof BaseDto) {
            $data = $data->toArray();
        }

        if ($data || is_array($data)) {
            if ($data instanceof Result || $data instanceof PaginatedListResult) {
                $this->response->setJsonContent($data);
            } else {
                $this->response->setJsonContent([
                    'data' => $data
                ]);
            }
        }
    }

    /**
     * Function for retrieving order field from params
     * eg. ?sort=name:asc|last_name|date:desc|priority,status:asc
     *
     * @param string $default
     *
     * @return string
     */
    public function order($default = ''): string
    {
        $sortParam = trim($this->params['sort']);

        $sortArray = explode('|', $sortParam);
        $parts = [];

        if (strlen($sortParam)) {
            foreach ($sortArray as $sortElement) {
                $splitted = explode(':', $sortElement);
                $fields = $splitted[0];
                $order = $splitted[1];

                // Split fields by comma, but not by comma in brackets
                // required for handling db functions as sort parameters
                $fieldsArray = preg_split('/,(?![^\(]*\))/', $fields);

                foreach ($fieldsArray as $field) {
                    if ($field && !$order) {
                        $parts[] = $field;
                    } elseif ($field && $order && (strtolower($order) == 'asc' || strtolower($order) == 'desc')) {
                        $parts[] = $field . ' ' . strtoupper($order);
                    }
                }
            }

            return implode(', ', $parts);
        } else {
            return $default;
        }
    }

    /**
     * Function for handling exceptions in rest controllers
     *
     * @throws AbstractHttpException
     * @throws BadRequestException
     */
    public function handleException(Exception $e): void
    {
        //  fixme: This is only a temporary fix - it will be replaced after switching for Unified API Controller.
        if ($e instanceof ValidationException) {
            throw FormValidationException::fromValidationErrors($e->getErrors());
        }

        $this->useExceptionHandler($e);
    }

    protected function useExceptionHandler(Throwable $exception): void
    {
        $handler = ExceptionHandler::parse($exception);

        $this->response->setStatusCode($handler->getHttpException()->getHttpCode());
        $this->response->setContent($handler->toJson());
    }

    public static function isFileAction(string $actionName): bool
    {
        return substr($actionName, 0, 4) == 'file' && ctype_upper((string) substr($actionName, 4, 1));
    }

    public static function isPrintAction(string $actionName): bool
    {
        return $actionName == 'print';
    }
}
