

const fs = require("fs-extra");
const path = require("path");
const express = require("express");
const chalk = require("chalk");
/**/
const RefData = require("./Models/RefData");
const DataBase = require("./Db/DataBase");

/**
 * @description Classe que executa a API
 * @type Class
 * @author Sillas S. Leal <sillas.s.leal@gmail.com>
 */
class Api {
    constructor(options = {}) {
        if(typeof options.swagger !== "string" || !options.swagger.length){
            throw new Error("Swagger file is invalid");
        }
        const swaggerFile = path.resolve(process.cwd(), options.swagger);
        if (!fs.existsSync(swaggerFile)) {
            throw new Error("Swagger file not found");
        }
        /**/
        this.swagger = require(swaggerFile);
        if (typeof this.swagger !== "object") {
            throw new Error("The Swagger file is not valid!!!");
        }
        if (options.port) {
            this.port = options.port;
        } else {
            this.port = "8080";
        }
        /**/
        this.model = new RefData(this.swagger);
        this.app = new express();
        this.db = new DataBase(options.db, this.swagger);
        this.generateRoutes();
    }

    /**
     * Método que gera as rotas da API
     * @throws {Error} Caso o swagger não possua os paths
     * @returns {undefined}
     */
    generateRoutes() {
        if (typeof this.swagger.paths !== "object") {
            throw new Error("O arquivo swagger informado não é válido!!!");
        }
        /**/

        Object.keys(this.swagger.paths).forEach(route => {
            Object.keys(this.swagger.paths[route]).forEach(method => {
                if (typeof this.app[method] !== "function") {
                    throw new Error(`The method '${method}' is invalid.`);
                }
                /**/
                this.app[method](route, (req, res) => {
                    this.request(req, res, route, method, this.swagger.paths[route][method]);
                });
            });
        });
    }

    /**
     * Método que gera a resposta de acorod com as definições do swagger
     * @param {object} request O objeto contendo os dados da requisição
     * @param {object} response O objeto contendo os dados e metodos para a resposta
     * @param {String} route A rota acessada
     * @param {Strng} method O método chamado
     * @param {object} defRouter As definições da rota em execução
     * @returns {undefined}
     */
    request(request, response, route, method, defRouter) {
        console.log(chalk.cyan("Request:"));
        let
                value,
                modelRet,
                ret,
                cod = 200,
                isError = false,
                name = "",
                parameters = {};

        //SECURITY
        //defRouter.security
        //SECURITY
        /**/
        //identify how input method(in route, form data, body data,...)
        //identify in the route config the parameters
        //read the data according the config
        if (Array.isArray(defRouter.parameters)) {
            try {
                defRouter.parameters.forEach(parameter => {
                    name = parameter.name;
                    switch (parameter.in.toLowerCase()) {
                        case "query":
                            value = request.query[name];
                            break;
                        case "body":
                            value = request.body[name];
                            break;
                        default:
                            //in path
                            value = request.path[name];
                            break;
                    }
                    /**/
                    if (value) {
                        parameters[name] = this.model.defData(parameter, value);
                        console.log("get");
                        console.log(parameters[name]);
                    }
                });
            } catch (e) {
                cod = 400;
            }
        }
        console.log("parameters");
        console.log(parameters);
        modelRet = defRouter.responses[cod];
//        ret = this.model.defData(modelRet, this.db.dbData(route, method, modelRet, parameters));
        ret = this.db.dbData(route, method, modelRet, parameters);
        response.status(cod);
        /**/
        if ((Array.isArray(defRouter.produces) &&
                defRouter.produces.indexOf("application/json")) ||
                defRouter.produces === "application/json") {
            console.log("ret");
            console.log(ret);
            response.json(ret);
//        } else {
//            response.send(ret);
        }
    }

    /**
     * Método que cria a estrutura de diretorios
     * @param {String} path O caminho completo
     */
    mkDir(path) {
        const dirs = path.split('/');
        let dir;
        dirs.forEach(i => {
            dir =
                    dir
                    ? `${dir}/${i}`
                    : i;
            if (!fs.existsSync(`/${dir}`)) {
                fs.mkdirSync(`/${dir}`);
            }
        });
    }

    /**
     * Método que inicia a API
     */
    start() {
        console.log(chalk.green(`Starting API: port=${this.port}`));
        /**/
        this.app.listen(this.port);
    }
}

module.exports = Api;
