<?php

use Phalcon\Mvc\Controller;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Regex;
use Phalcon\Validation\Validator\Numericality;
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\Digit;
use Phalcon\Validation\Validator\Email;
use Phalcon\Validation\Validator\Date as DateValidator;
use Phalcon\Validation\Validator\InclusionIn;
use Phalcon\Validation\Validator\StringLength as StringLength;
use Phalcon\Validation\Validator\Between;
use Phalcon\Validation\Validator\Confirmation;
use Jaga\Tools\Validator\Max;
use Jaga\Tools\Validator\Min;

class BaseController extends Controller
{
    protected $userID;

    /**
     * Alternative __construct() method provided by phalcons
     * save logged in user id to variable, to prevent repetitive chain method call
     */
    public function onConstruct()
    {
        if (!isset($this->shared->jwt) || empty($this->shared->jwt)) {
            $this->userID = null;
        } else {
            $this->userID = $this->shared->user->um_id;
        }

        return true;
    }

    public function isAuthenticated()
    {
        if (!isset($this->shared->jwt) || empty($this->shared->jwt)) {
            return false;
        }

        return true;
    }

    public function checkIsAuthenticated()
    {
        if (!$this->isAuthenticated()) {
            throw new CustomErrorException(BaseResponse::INVALID_AUTHENTICATION_AUTHORIZATION, 'Invalid Authentication');
        }
    }

    public function hasScopes($requiredScopes)
    {
        if (!$this->isAuthenticated()) {
            return false;
        }

        $ownedScopes = explode(' ', $this->shared->jwt->scope);

        $hasAllScope = true;

        foreach ($requiredScopes as $key => $requiredScope) {
            if (!in_array($requiredScope, $ownedScopes)) {
                $hasAllScope = false;
            }
        }

        return $hasAllScope;
    }

    public function hasOneOfScopes($requiredScopes)
    {
        if (!$this->isAuthenticated()) {
            return false;
        }

        $ownedScopes = explode(' ', $this->shared->jwt->scope);

        $hasOneScope = false;

        foreach ($requiredScopes as $key => $requiredScope) {
            if (in_array($requiredScope, $ownedScopes)) {
                $hasOneScope = true;
            }
        }

        return $hasOneScope;
    }

    public function checkScopes($requiredScopes)
    {
        if (!$this->hasScopes($requiredScopes)) {
            throw new CustomErrorException(BaseResponse::INVALID_AUTHENTICATION_AUTHORIZATION, 'User is not allowed to perform this action');
        }
    }

    public function checkOneOfScopes($requiredScopes)
    {
        $this->checkIsAuthenticated();

        $ownedScopes = explode(' ', $this->shared->jwt->scope);

        foreach ($requiredScopes as $key => $requiredScope) {
            if (in_array($requiredScope, $ownedScopes)) {
                return;
            }
        }

        throw new CustomErrorException(BaseResponse::INVALID_AUTHENTICATION_AUTHORIZATION, 'User is not allowed to perform this action');
    }

    protected function hasRole($role)
    {
        $this->checkIsAuthenticated();

        if(is_array($role)) {
            return false;
        }

        $roles = $this->shared->user->roles;
        foreach ($roles as $r) {
            if ($role == $r->name) {
                return true;
            }
        }

        return false;
    }

    protected function hasOneOfRoles($roles)
    {
        $this->checkIsAuthenticated();

        if(!is_array($roles)) {
            return false;
        }

        $ownroles = $this->shared->user->roles;
        foreach ($ownroles as $r) {
            if (in_array($r->name, $roles)) {
                return true;
            }
        }

        return false;
    }

    public function pHasRole($role)
    {
        return $this->hasRole($role);
    }

    public function baseValidate($payload, $rules)
    {
        $result = [];
        $validation = new Validation();

        foreach ($rules as $attributeName => $rule) {
            $attributeValue = isset($payload[$attributeName]) ? $payload[$attributeName] : null;
            $result[$attributeName] = $attributeValue;

            if (!empty($rule) && !empty($rule['validators'])) {

                foreach ($rule['validators'] as $key => $element) {
                    $validator = null;
                    $validatorName = null;
                    $validatorParams = [];

                    if (is_array($element)) {
                        $validatorName = $element['name'];
                        $validatorParams = $element['params'];
                    } else {
                        $validatorName = $element;
                    }

                    $lowerValidatorName = strtolower($validatorName);

                    if ($lowerValidatorName == 'required') {
                        $validator = new PresenceOf();
                    } else if ($lowerValidatorName == 'digit') {
                        $validator = new Digit(['allowEmpty' => true]);
                    } else if ($lowerValidatorName == 'email') {
                        $validator = new Email(['allowEmpty' => true]);
                    } else if ($lowerValidatorName == 'numeric') {
                        $validator = new Numericality(['allowEmpty' => true]);
                    } else if ($lowerValidatorName == 'uuid') {
                        $validator = new RegexValidator([
                            "pattern" => "/[a-f0-9]{8}\-[a-f0-9]{4}\-4[a-f0-9]{3}\-(8|9|a|b)[a-f0-9]{3‌​}\-[a-f0-9]{12}/",
                            "message" => "Invalid UUID",
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'date') {
                        $validator = new DateValidator([
                            "format" => "Y-m-d",
                            "message" => "Invalid Date Format",
                            'allowEmpty' => true
                        ]);
                    } else if ($lowerValidatorName == 'datetime') {
                        $validator = new DateValidator([
                            "format" => "Y-m-d H:i:s",
                            "message" => "Invalid Date Format",
                            'allowEmpty' => true
                        ]);
                    } else if ($lowerValidatorName == 'inclusion_in') {
                        $validator = new InclusionIn([
                            "message" => "must be one of [" . implode(",", $validatorParams) . "]",
                            "domain" => $validatorParams,
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'string_length') {
                        $validator = new StringLength([
                            "min" => $validatorParams,
                            "max" => $validatorParams,
                            "messageMinimum" => $attributeName . " must have " . $validatorParams . " characters",
                            "messageMaximum" => $attributeName . " must have " . $validatorParams . " characters",
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'min_string') {
                        $validator = new StringLength([
                            "min" => $validatorParams,
                            "messageMinimum" => $attributeName . " must have minimum " . $validatorParams . " characters",
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'max_string') {
                        $validator = new StringLength([
                            "max" => $validatorParams,
                            "messageMaximum" => $attributeName . " must have maximum " . $validatorParams . " characters",
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'min') {
                        $validator = new Min([
                            "min" => $validatorParams,
                            "message" => $attributeName . " value must be more than or equal to " . $validatorParams,
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'max') {
                        $validator = new Max([
                            "max" => $validatorParams,
                            "message" => $attributeName . " value must be less than or equal to " . $validatorParams,
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'between') {
                        $min = $validatorParams['min'];
                        $max = $validatorParams['max'];
                        $validator = new Between([
                            "minimum" => $min,
                            "maximum" => $max,
                            "message" => "Value must be be between " . $min . " and " . $max,
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'alpha_dash') {
                        $validator = new Regex([
                            "pattern" => "/^[0-9A-Za-z.\-_]+$/u",
                            //"pattern" => '/^[\pL\pM\pN_-].+$/u',
                            "message" => "Field ". $attributeName." may only contain letters, numbers, dashes, underscores and dots.",
                            ['allowEmpty' => true]
                        ]);
                    } else if ($lowerValidatorName == 'confirm_password') {
                        $validator = new Confirmation([
                                "message" => [
                                    "password" => "Password doesn't match confirmation",
                                ],
                                "with" => [
                                    "password" => "confirm_password",
                                ],
                            ]);
                    }

                    $validation->add($attributeName, $validator);
                }
            }
        }

        $messages = $validation->validate($payload);
        $errorMessages = [];

        if (count($messages) > 0) {

            foreach ($messages as $message) {
                $errorMessages[] = [
                    'field' => $message->getField(),
                    'message' => $message->getMessage()
                ];
            }
        }

        return [
            'result' => $result,
            'errors' => $errorMessages
        ];
    }

    public function validate($payload, $rules)
    {
        $validationResult = $this->baseValidate($payload, $rules);

        if (count($validationResult['errors']) > 0) {
            throw new ValidationException($validationResult['errors']);
        }

        return $validationResult['result'];
    }

    public function rawQuery($query, $params = [], $connectionName = "db")
    {
        try {
            $q = $this->$connectionName->query($query, $params);
        } catch (Exception $e) {
            if (getEnv('APP_ENV') === 'production') {
                throw new CustomErrorException(BaseResponse::DATA_NOT_AVAILABLE, 'Data not available');
            } else {
                throw new Exception($e);
            }
        }
        
        $q->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
        $pdo = $q->getInternalResult();
        $rows = $q->fetchAll();
        $float_cols = [
            'float', 'float4', 'float8', 'double', 'numeric', // for postgresql
            'LONGLONG', 'NEWDECIMAL' // for mysql
        ];
        $int_cols = ['int8'];
        $ncol = $pdo->columnCount();
        for ($i = 0; $i < $ncol; $i++) {
            $col = $pdo->getColumnMeta($i);
            $cname = $col['name'];
            $type = isset($col['native_type']) ? $col['native_type'] : null;
            if (in_array($type, $float_cols)) {
                foreach ($rows as &$row) {
                    $row[$cname] = floatval($row[$cname]);
                }
            }
            if (in_array($type, $int_cols)) {
                foreach ($rows as &$row) {
                    $row[$cname] = intval($row[$cname]);
                }
            }
        }
        return $rows;
    }

    protected function countRelatedTeam($userId, $instansiId)
    {
        $teamCount = $this->rawQuery('
            SELECT count(*) as total
            FROM jaga.korwil_teams t
            WHERE id IN (
                SELECT team_id
                FROM jaga.korwil_team_user
                WHERE team_id = t.id
                    AND user_id = :user_id
            ) AND id IN (
                select team_id
                FROM jaga.korwil_team_instansi
                WHERE team_id = t.id
                    AND instansi_id = :instansi_id
            )
        ', [
            'user_id' => $userId,
            'instansi_id' => $instansiId
        ])[0]['total'];

        return $teamCount;
    }

    protected function getAndValidateImportFile($name, $allowedTypes)
    {
        if (!isset($_FILES[$name])) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'File wajib diunggah');
        }

        if (!in_array($_FILES[$name]['type'], $allowedTypes)) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Tipe CSV tidak valid: ' . $_FILES[$name]['type']);
        }

        return $_FILES[$name];
    }

    protected function getAndValidateImportFile2($name, $allowedTypes, $uploadedFiles)
    {
        if (empty($uploadedFiles)) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'File wajib diunggah');
        }

        $file = null;

        foreach ($uploadedFiles as $key => $uploadedFile) {
            if ($uploadedFile->getKey() == $name) {
                if (!in_array($uploadedFile->getRealType(), $allowedTypes)) {
                    throw new CustomErrorException(
                        BaseResponse::INVALID_REQUEST,
                        'Tipe File tidak valid: '
                            . $_FILES[$name]['type']
                            . ' . Expected:  '
                            . implode(',', $allowedTypes)
                    );
                }

                $file = $uploadedFile;
            }
        }

        if (empty($file)) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'File tidak ditemukan: ' . $name);
        }

        return $file;
    }

    public function throwValidationException($errors)
    {
        $error_messages = [];

        foreach ($errors as $key => $error) :
            $error_messages[] = [
                "field" => $error->getField(),
                "message" => $error->getMessage()
            ];
        endforeach;

        throw new ValidationException($error_messages);
    }

    public function filterExpectedRequest($request, $rules) {

        $this->throwIfNotArrayRequest($request);

        return $this->validate($request, $rules);
    }

    /**
     * Handling error json input caused by comma
     * i.e : {"id":1,}
     */
    public function throwIfNotArrayRequest($request)
    {
        if (!is_array($request)) {
            throw new CustomErrorException(
                BaseResponse::INVALID_REQUEST,
                'Invalid json request'
            );
        }

        return $request;
    }

    public function getIdInstansi() {
        return $this->shared->user->id_instansi;
    }

    public function throwObjectValidationException($object) {
        $errors = $object->getMessages();
        $error_messages = [];

        foreach ($errors as $key => $error) {
            $error_messages[] = ["field" => $error->getField(), "message" => $error->getMessage()];
        }

        throw new ValidationException($error_messages);
    }

    public function getDefaultLimit($params) {
        return array_key_exists('limit', $params) ? (intval($params['limit']) > 0 ? $params['limit'] : 999) : 10;
    }
}
