<?php

namespace Velis\Http;

use Phalcon\Http\Request as PhalconRequest;
use Phalcon\Mvc\Controller;
use stdClass;
use Velis\App;
use Velis\Filter;
use Velis\Mvc\Controller\BaseController;
use Velis\Mvc\Controller\RestfulController;

/**
 * HTTP Request
 * @author Olek Procki <olo@velis.pl>
 */
class Request extends PhalconRequest
{
    public const API_v1 = 'v1';
    public const API_v2 = 'v2';

    /**
     * Action access types
     */
    const ACCESS_PUBLIC = 'public';
    const ACCESS_IFRAME = 'iframe';

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

    /**
     * Internal flag set when file names were secured against XSS attacks
     * @var bool
     */
    private static $filesFiltered = false;

    /**
     * Constructor - convert file names to be secure
     */
    public function __construct()
    {
        $this->id = md5(microtime());

        if ($this->hasFiles() && !self::$filesFiltered) {
            foreach ($_FILES as $var => $file) {
                if (is_array($file['name'])) {
                    foreach ($file['name'] as $index => $name) {
                        $_FILES[$var]['name'][$index] = Filter::filterXss($name);
                    }
                } else {
                    $_FILES[$var]['name'] = Filter::filterXss($file['name']);
                }
            }
            self::$filesFiltered = true;
        }
    }

    /**
     * @return string
     */
    public function getId(): string
    {
        return $this->id;
    }

    /**
     * Returns current controller instance
     * @return Controller
     */
    protected function _getController()
    {
        $controller = null;

        if ($dispatcher = App::getService('dispatcher')) {
            $controller = $dispatcher->getActiveController();
            if (!$controller) {
                $controller = $dispatcher->getLastController();
            }
        }

        return $controller;
    }

    /**
     * Returns true if request comes from ajax call
     *
     * @return bool
     */
    public function isAjax(): bool
    {
        $ignoreInstant = func_num_args() ? func_get_arg(0) : false;

        if (parent::isAjax()) {
            if ($this->getHeader('X_AJAX_IGNORE')) {
                return false;
            } elseif ($ignoreInstant && $this->isInstant()) {
                return false;
            } else {
                return true;
            }
        }
        return false;
    }


    /**
     * Returns true if request is authorization type (api)
     * @return bool
     */
    public function isAuthorization()
    {
        $loginAction        = [['api', 'rest', 'login'], ['api', 'login', 'login'], ['admin', 'api', 'login']];
        $refreshTokenAction = [['api', 'rest', 'refresh-token'], ['api', 'login', 'refresh-token'], ['admin', 'api', 'refresh-token']];
        $logoutAction       = [['api', 'rest', 'logout'], ['api', 'login', 'logout'], ['admin', 'api', 'logout']];
        $qrCodeLogin = [['api', 'rest', 'login-qr'], ['api', 'login', 'login-qr']];

        $requestAction = [
            App::$registry['moduleName'],
            App::$registry['controllerName'],
            App::$registry['actionName'],
        ];

        $body = $this->getJsonRawBody(true);

        if (in_array($requestAction, $loginAction)) {
            if (
                $this->getPost('username') &&
                $this->getPost('password')
            ) {
                return true;
            } elseif ($body['username'] && $body['password']) {
                return true;
            } elseif ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
                return true;
            }
        }

        if (in_array($requestAction, $refreshTokenAction) && $this->getPost('refresh-token')) {
            return true;
        }

        if (
            (
                in_array($requestAction, $qrCodeLogin)
                || in_array($requestAction, $logoutAction)
            )
            && $this->authorization()
        ) {
            return true;
        }

        return false;
    }


    /**
     * Returns true if it's a login request
     * @return bool
     */
    public function isLogin()
    {
        $loginActions = [
            ['application', 'login', 'login'],
            ['api', 'rest', 'login'],
            ['api', 'login', 'login'],
            ['admin', 'api', 'login']
        ];

        $requestAction = [
            App::$registry['moduleName'] ?? null,
            App::$registry['controllerName'] ?? null,
            App::$registry['actionName'] ?? null
        ];

        return in_array($requestAction, $loginActions);
    }


    /**
     * Returns refresh_hash if it's a token refresh request
     * @return bool
     */
    public function isTokenRefresh()
    {
        $requestAction = [
            App::$registry['moduleName'],
            App::$registry['controllerName'],
            App::$registry['actionName']
        ];

        if ($requestAction == ['api', 'rest', 'refresh-token']) {
            return $this->getPost('refresh-token');
        }

        return false;
    }


    /**
     * Returns true if request is authorization type (api)
     * @return bool
     */
    public function isHidden()
    {
        $hiddenActions = [
            ['user', 'settings', 'save-user-info'],
            ['user', 'settings', 'save-user-password'],
            ['user', 'index', 'activation'],
        ];

        $requestAction = [
            App::$registry['moduleName'],
            App::$registry['controllerName'],
            App::$registry['actionName'],
        ];

        return in_array($requestAction, $hiddenActions);
    }


    /**
     * Checks if request was instant content request for single page mode
     * @return bool
     */
    public function isInstant()
    {
        return $this->getHeader('X_INSTANT_REQUEST');
    }


    /**
     * Checks if last invoked action was public one
     * @return bool
     */
    public function isPublicAction()
    {
        if (App::isConsole()) {
            return false;
        }

        $controller = $this->_getController();

        if ($controller instanceof BaseController) {
            return $controller->isPublicAction();
        } elseif ($controller instanceof RestfulController) {
            return true;
        }

        return false;
    }


    /**
     * Checks if last invoked action can be extecuted within an iframe
     * @return bool
     */
    public function isFrameAllowedAction()
    {
        if (App::isConsole()) {
            return false;
        }

        $controller = $this->_getController();
        if ($controller instanceof BaseController) {
            return $controller->isFrameAllowedAction();
        }
        return false;
    }


    /**
     * Returns true if request correctly authenticate via basic authorized
     * @return bool
     */
    public function isBasicAuth()
    {
        $auth = $this->getBasicAuth();

        if ($auth['username'] == App::$config->http->user && $auth['password'] == App::$config->http->password) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * Returns true if it's api call
     * @return bool
     */
    public function isApi()
    {
        return stripos($_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'], $_SERVER['SERVER_NAME'] . '/api') === 0
            || stripos($_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'], $_SERVER['SERVER_NAME'] . '/rest') === 0
            || str_contains($_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'], '/api/')
            || str_contains($_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'], '/api?')
            || str_ends_with($_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'], '/api');
    }

    public function isApiVersion(string $version): bool
    {
        if (!$this->isApi()) {
            return false;
        }

        return str_contains($_SERVER['REQUEST_URI'], $version) !== false;
    }


    /**
     * Returns true if application is in mobile mode
     * @return bool
     */
    public function isMobile()
    {
        if (in_array($_SERVER['REQUEST_URI'], array('/m', '/m/'))) {
            return true;
        }
        if (preg_match('~^/m/?\?~', $_SERVER['REQUEST_URI'])) {
            return true;
        }

        return false;
    }


    /**
     * Returns true if mobile browser used
     * @return int
     */
    public function isMobileBrowser()
    {
        $isMobile = preg_match('/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i', $_SERVER['HTTP_USER_AGENT']) ? 1 : 0;
        return $isMobile;
    }

    /**
     * @return bool
     */
    public function isMobileApp(): bool
    {
        return in_array($this->getHeader('Platform'), ['Android', 'iOS']);
    }


    /**
     * @param int $no [version of IE to check]
     * Returns version of IE browser
     * @return bool/int
     */
    public function isIE($no = null)
    {
        $trident = [
            '3' => '7',
            '4' => '8',
            '5' => '9',
            '6' => '10',
            '7' => '11',
        ];

        $math1 = explode("MSIE ", $_SERVER['HTTP_USER_AGENT'])[1];
        $math2 = explode("Trident/", $_SERVER['HTTP_USER_AGENT'])[1];
        $math3 = explode("Edge/", $_SERVER['HTTP_USER_AGENT'])[1];
        if ($math1) {
            $version = (int)$math1;
        } elseif ($math2) {
            $version = $trident[(int)$math2];
        } elseif ($math3) {
            $version = "Edge";
        }
        if ($no) {
            return $no == $version;
        }
        return $version;
    }


    /**
     * Returns request authorization header value
     * @return string
     */
    public function authorization()
    {
        if ($auth = $this->getHeader('AUTHORIZATION')) {
            return $auth;
        } else {
            $headers = getallheaders();

            return $headers['Authorization'] ?? $headers['authorization'] ?? null;
        }
    }


    /**
     * Returns true if request require JSON response
     * @return bool
     */
    public function acceptJson()
    {
        return $this->getHeader('ACCEPT') == 'application/json';
    }


    /**
     * Returns request protocol
     * @return string
     */
    public function getProtocol()
    {
        if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
            return $_SERVER['HTTP_X_FORWARDED_PROTO'];
        } else {
            return !empty($_SERVER['HTTPS']) ? "https" : "http";
        }
    }

    /**
     * Gets decoded JSON HTTP raw request body.
     * That's a workaround for Phalcon's `getJsonRawBody()` method which doesn't work properly
     * Problem was described in tickets: #70313 and #70335.
     *
     * @return array|bool|stdClass
     */
    public function getJsonRawBody(bool $associative = false)
    {
        return json_decode(file_get_contents('php://input'), $associative);
    }
}
