/**
 * Copyright OpenJS Foundation and other contributors, https://openjsf.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

const Enquirer = require('enquirer');
const color = require('ansi-colors');

const mustache = require("mustache");
const fs = require("fs");
const path = require("path");

let bcrypt;
try { bcrypt = require('@node-rs/bcrypt'); }
catch(e) { bcrypt = require('bcryptjs'); }

function prompt(opts) {
    const enq = new Enquirer();
    return enq.prompt(opts);
}

/**
 *  1. identify userdir
 *  2. check if settings file already exists
 *  3. Enable projects feature with version control?

 *  3. get flowFile name
 *  3. get credentialSecret
 *  4. ask to setup adminAuth
 *    - prompt for username
 *    - prompt for password
 */

async function loadTemplateSettings() {
    const templateFile = path.join(__dirname,"resources/settings.js.mustache");
    return fs.promises.readFile(templateFile,"utf8");
}

async function fillTemplate(template, context) {
    return mustache.render(template,context);
}

function heading(str) {
    console.log("\n"+color.cyan(str));
    console.log(color.cyan(Array(str.length).fill("=").join("")));
}
function message(str) {
    console.log(color.bold(str)+"\n");
}

async function promptSettingsFile(opts) {
    const defaultSettingsFile = path.join(opts.userDir,"settings.js");
    const responses = await prompt([
        {
            type: 'input',
            name: 'settingsFile',
            initial: defaultSettingsFile,
            message: "Settings file",
            onSubmit(key, value, p) {
                p.state.answers.exists = fs.existsSync(value);
            }
        },
        {
            type: 'select',
            name: 'confirmOverwrite',
            initial: "No",
            message: 'That file already exists. Are you sure you want to overwrite it?',
            choices: ['Yes', 'No'],
            result(value) {
                return value === "Yes"
            },
            skip() {
                return !this.state.answers.exists
            }
        }
    ]);

    if (responses.exists && !responses.confirmOverwrite) {
        return promptSettingsFile(opts);
    }
    // const alreadyExists = await fs.exists(responses.settingsFile);
    // responses.exists =
    return responses;
}

async function promptUser() {
    const responses = await prompt([
        {
            type: 'input',
            name: 'username',
            message: "Username",
            validate(val) { return !!val.trim() ? true: "Invalid username"}
        },
        {
            type: 'password',
            name: 'password',
            message: "Password",
            validate(val) {
                if (val.length < 8) {
                    return "Password too short. Must be at least 8 characters"
                }
                return true
            }
        },
        {
            type: 'select',
            name: 'permissions',
            message: "User permissions",
            choices: [ {name:"full access", value:"*"}, {name:"read-only access", value:"read"}],
            result(value) {
                return this.find(value).value;
            }
        }
    ])
    responses.password = bcrypt.hashSync(responses.password, 8);
    return responses;
}

async function promptSecurity() {
    heading("User Security");

    const responses = await prompt([
        {
            type: 'select',
            name: 'adminAuth',
            initial: "Yes",
            message: 'Do you want to setup user security?',
            choices: ['Yes', 'No'],
            result(value) {
                return value === "Yes"
            }
        }
    ])
    if (responses.adminAuth) {
        responses.users = [];
        while(true) {
            responses.users.push(await promptUser());
            const resp = await prompt({
                type: 'select',
                name: 'addMore',
                initial: "No",
                message: 'Add another user?',
                choices: ['Yes', 'No'],
                result(value) {
                    return value === "Yes"
                }
            })
            if (!resp.addMore) {
                break;
            }
        }
    }
    return responses;
}

async function promptProjects() {
    heading("Projects");
    message("The Projects feature allows you to version control your flow using a local git repository.");
    const responses = await prompt([
        {
            type: 'select',
            name: 'enabled',
            initial: "No",
            message: 'Do you want to enable the Projects feature?',
            choices: ['Yes', 'No'],
            result(value) {
                return value === "Yes";
            }
        },
        // {
        //     type: 'select',
        //     name: '_continue',
        //     message: 'Node-RED will help you create your project the first time you access the editor.',
        //     choices: ['Continue'],
        //     skip() {
        //         return !this.state.answers.enabled;
        //     }
        // },
        {
            type: 'select',
            name: 'workflow',
            message: 'What project workflow do you want to use?',
            choices: [
                {value: 'manual', name: 'manual - you must manually commit changes'},
                {value: 'auto', name: 'auto - changes are automatically committed'}
            ],
            skip() {
                return !this.state.answers.enabled;
            },
            result(value) {
                return this.find(value).value;
            }
        }
    ])
    // delete responses._continue;
    return responses
}

async function promptFlowFileSettings() {
    heading("Flow File settings");
    const responses = await prompt([
        {
            type: 'input',
            name: 'flowFile',
            message: 'Enter a name for your flows file',
            default: 'flows.json'
        },
        {
            type: 'password',
            name: 'credentialSecret',
            message: 'Provide a passphrase to encrypt your credentials file'
        }
    ])
    return responses
}

async function promptNodeSettings() {
    heading("Node settings");
    const responses = await prompt([
        {
            type: 'select',
            name: 'functionExternalModules',
            message: 'Allow Function nodes to load external modules? (functionExternalModules)',
            initial: 'Yes',
            choices: ['Yes', 'No'],
            result(value) {
                return value === "Yes"
            },
        }
    ]);
    return responses;
}
async function promptEditorSettings() {
    heading("Editor settings");
    const responses = await prompt([
        {
            type: 'select',
            name: 'theme',
            message: 'Select a theme for the editor. To use any theme other than "default", you will need to install @node-red-contrib-themes/theme-collection in your Node-RED user directory.',
            initial: 'default',
            choices: [ "default", "aurora", "cobalt2", "dark", "dracula", "espresso-libre", "github-dark", "github-dark-default", "github-dark-dimmed", "midnight-red", "monoindustrial", "monokai", "monokai-dimmed", "noctis", "oceanic-next", "oled", "one-dark-pro", "one-dark-pro-darker", "solarized-dark", "solarized-light", "tokyo-night", "tokyo-night-light", "tokyo-night-storm", "totallyinformation", "zenburn"],
        },
        {
            type: 'select',
            name: 'codeEditor',
            message: 'Select the text editor component to use in the Node-RED Editor',
            initial: 'monaco',
            choices: [ {name:"monaco (default)", value:"monaco"}, {name:"ace", value:"ace"} ],
            result(value) {
                return this.find(value).value;
            }
        }
    ]);
    return responses;
}
async function command(argv, result) {
    const config = {
        intro: `Node-RED Settings created at ${new Date().toUTCString()}`,
        flowFile: "flows.json",
        editorTheme: ""
    };

    heading("Node-RED Settings File initialisation");
    message(`This tool will help you create a Node-RED settings file.`);


    const userDir = argv["u"] || argv["userDir"] || path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");

    const fileSettings = await promptSettingsFile({userDir});

    const securityResponses = await promptSecurity();
    if (securityResponses.adminAuth) {
        let adminAuth = {
            type: "credentials",
            users: securityResponses.users
        };
        config.adminAuth = JSON.stringify(adminAuth, null, 4).replace(/\n/g,"\n    ");
    }

    const projectsResponses = await promptProjects();
    let flowFileSettings = {};
    if (!projectsResponses.enabled) {
        flowFileSettings = await promptFlowFileSettings();
        config.flowFile = flowFileSettings.flowFile;
        if (flowFileSettings.hasOwnProperty("credentialSecret")) {
            config.credentialSecret = flowFileSettings.credentialSecret?`"${flowFileSettings.credentialSecret}"`:"false"
        }
        config.projects = {
            enabled: false,
            workflow: "manual"
        }
    } else {
        config.projects = projectsResponses
    }
    const editorSettings = await promptEditorSettings();
    config.codeEditor = editorSettings.codeEditor;
    if (editorSettings.theme !== "default") {
        config.editorTheme = editorSettings.theme
    }
    const nodeSettings = await promptNodeSettings();
    config.functionExternalModules = nodeSettings.functionExternalModules;


    const template = await loadTemplateSettings();
    const settings = await fillTemplate(template, config)

    const settingsDir = path.dirname(fileSettings.settingsFile);
    await fs.promises.mkdir(settingsDir,{recursive: true});
    await fs.promises.writeFile(fileSettings.settingsFile, settings, "utf-8");


    console.log(color.yellow(`\n\nSettings file written to ${fileSettings.settingsFile}`));

    if (config.editorTheme) {
        console.log(color.yellow(`To use the '${config.editorTheme}' editor theme, remember to install @node-red-contrib-themes/theme-collection in your Node-RED user directory`))
    }
}
command.alias = "init";
command.usage = command.alias;
command.description = "Initialise a Node-RED settings file";

module.exports = command;
