import path from "path";
import fs from "fs";
import {spawn} from "child_process";
import {CloudDirectorConfig} from '@vcd/node-client';
import inquirer from "inquirer";
import * as cp from "child_process"
import * as chokidar from "chokidar";

export class UiEmulatorUtils {

    private static loadJsonConfig(rootDir: string, name: string) {
        const jsonPath = path.resolve(rootDir, name);
        if (!fs.existsSync(jsonPath)) {

            return null;
        }
        const fileContent = fs.readFileSync(jsonPath).toString();
        return JSON.parse(fileContent);
    }

    private static storeJsonConfig(rootDir: string, name: string, content: any) {
        const jsonPath = path.resolve(rootDir, name);
        fs.writeFileSync(jsonPath, JSON.stringify(content, null, 2));
    }

    private static discoverPluginModule(packageRoot: string, elementsRootRelativePath: string, elementFolderName: string) {
        const srcRoot = path.join(elementFolderName, 'src');
        return {
            elementsRootRelativePath,
            srcRoot,
            elementFolderName,
        };
    }

    /**
     * Configures and serves the ui-emulator angular application, hosting provided ui plugin elements.
     * @param uiEmulatorConfigRoot - absolute root path to ui plugin folder
     * @param elementsRootRelativePath - relative path to root dir containing all plugin folders
     * @param elementFolderNames - all ui plugin folders
     * @param vcdConfig - VCD Configuration object
     * @param serveArgs - Arguments passed to ng serve
     */
    public static async serve(uiEmulatorConfigRoot: string,
                              elementsRootRelativePath: string,
                              elementFolderNames: string[],
                              vcdConfig: {
                                  jwt: string,
                                  authtoken?: string,
                                  cellUrl: string,
                                  scope: "tenant" | "provider",
                                  orgName: string,
                                  orgId: string,
                                  locale: string,
                              },
                              serveArgs?: string[]
    ) {

        const rootDir = path.join(uiEmulatorConfigRoot, '.env'); // Extract as optional parameter?

        const emulatorPath = "node_modules/@vcd/ui-emulator";
        const emulatorRoot = path.resolve(emulatorPath);

        const angularJson = this.loadJsonConfig(emulatorRoot, 'angular.json');
        const tsconfigJson = this.loadJsonConfig(emulatorRoot, 'tsconfig.app.json');
        const environment = this.loadJsonConfig(rootDir, 'environment.json');
        const proxyConfig = this.loadJsonConfig(rootDir, 'proxy.conf.json');

        const argIndexPort = serveArgs.findIndex((argument: string) => {
            return argument === "--port";
        });
        let defaultDevServerPort: string = "4200";
        if (argIndexPort !== -1) {
            const serverPort = serveArgs[argIndexPort + 1];
            defaultDevServerPort = serverPort;
        }

        const bootstrapDetails = {
            "devMode": false,
            "locale": vcdConfig.locale || "en",
            "scope": vcdConfig.scope,
            "builtInExtensions": [],
            "thirdPartyExtensions": {
                "hyperplugin": "/assets/hyperplugin",
                "hyperplugin2": "/assets/hyperplugin2",
            },
            "cell": {
                "address": `http://localhost:${defaultDevServerPort}`,
                "organization": vcdConfig.orgName,
            },
            "flexUrl": "",
        };

        let pluginModules: {
            elementsRootRelativePath: string;
            srcRoot: string;
            elementFolderName: string;
        }[] = [];

        try {
            console.log('Setting auth token');

            environment.credentials = {
                jwt: vcdConfig.jwt,
            };

            if (vcdConfig.authtoken) {
                environment.credentials.authtoken = vcdConfig.authtoken;
            }

            console.log('Updating proxy config');
            Object.keys(proxyConfig).forEach(key => {
                proxyConfig[key].target = vcdConfig.cellUrl;
            });
            console.log('Updating Plugins');

            if (vcdConfig.orgId) {
                bootstrapDetails.cell["orgId"] = vcdConfig.orgId;
            }

            pluginModules = elementFolderNames.map((element) => {
                bootstrapDetails.thirdPartyExtensions[element] = `/assets/${element}`;

                return this.discoverPluginModule(uiEmulatorConfigRoot, elementsRootRelativePath, element)
            });

            console.log('Updating angular.json');
            angularJson.projects.emulator.sourceRoot = `${emulatorPath}/src`;
            angularJson.projects.emulator.architect.build.options.index = `${emulatorPath}/src/index.html`;
            angularJson.projects.emulator.architect.build.options.main = `${emulatorPath}/src/main.ts`;
            angularJson.projects.emulator.architect.build.options.tsConfig = "tsconfig.emulator.json";
            angularJson.projects.emulator.architect.build.options.styles = [
                "node_modules/@clr/ui/clr-ui.min.css",
                "node_modules/@clr/icons/clr-icons.min.css",
                "node_modules/@vcd/ui-emulator/src/styles.scss"
            ];
            angularJson.projects.emulator.architect.serve.configurations.development = {
                "browserTarget": "emulator:build",
                "proxyConfig": ".env/proxy.conf.runtime.json"
            };

            angularJson.projects.emulator.architect.build.options.assets = [
                `${emulatorPath}/src/favicon.ico`,
                `${emulatorPath}/src/assets`,
                ...pluginModules.map((pm) => {
                    return {
                        glob: '**/*',
                        input: `./${path.join(pm.elementsRootRelativePath, pm.srcRoot, "../dist")}`,
                        output: `assets/${pm.elementFolderName}`
                    };
                })
            ];
            angularJson.projects.emulator.architect.build.options.fileReplacements = [
                {
                  "replace": "node_modules/@vcd/ui-emulator/.env/environment.runtime.json",
                  "with": ".env/environment.runtime.json"
                }
            ];
            console.log('Updating tsconfig.emulator.json');
            tsconfigJson.extends = `./${emulatorPath}/tsconfig.json`;
            tsconfigJson.files = [
                `${emulatorPath}/src/main.ts`
            ];
            tsconfigJson.include = [
                '.env/*.json',
                `${emulatorPath}/src/**/*.ts`,
            ];
            tsconfigJson.exclude = [
                `${emulatorPath}/src/assets`,
            ];

            const windowBootstrapDetails = `
                window["_bootstrapDetails"] = ${JSON.stringify(bootstrapDetails, null, 4)}
            `.trim();
            fs.writeFileSync(path.resolve(emulatorRoot, "src", "bootstraDetails.js"), windowBootstrapDetails);
        } catch (e) {
            console.log('Error configuring environment.', e);
        } finally {
            this.storeJsonConfig(rootDir, 'environment.runtime.json', environment);
            this.storeJsonConfig(rootDir, 'proxy.conf.runtime.json', proxyConfig);
            this.storeJsonConfig(uiEmulatorConfigRoot, 'angular.json', angularJson);
            this.storeJsonConfig(uiEmulatorConfigRoot, 'tsconfig.emulator.json', tsconfigJson);
        }

        const uiPluginBuilds = pluginModules.map((pluginModule) => {
            const pluginSrcRoot = path.resolve(path.join(pluginModule.elementsRootRelativePath, pluginModule.srcRoot));
            const pluginRootDir = path.resolve(path.join(pluginSrcRoot, "./../"));

            return this.buildUIPlugin(pluginRootDir);
        });

        Promise.all(uiPluginBuilds)
            .then(() => {
                spawn('ng', ['serve', ...(serveArgs ? serveArgs : [])], {
                    cwd: uiEmulatorConfigRoot,
                    stdio: 'inherit'
                });

                pluginModules.forEach((pluginModule) => {
                    const pluginSrcRoot = path.resolve(path.join(pluginModule.elementsRootRelativePath, pluginModule.srcRoot));
                    console.log(`Watching ${pluginSrcRoot} for changes`);
                    this.watchUIPluginsSources(pluginSrcRoot);
                });
            });

        return Promise.resolve();
    }

    private static watchUIPluginsSources(pluginSrcRoot: string, skipFirstEvent: boolean = true) {
        const watcher = chokidar.watch(pluginSrcRoot, {ignored: /^\./, persistent: true});

        const pluginRootDir = path.resolve(path.join(pluginSrcRoot, "./../"));
        let child_process = null;
        watcher.on("all", (event, path) => {
            if (child_process || skipFirstEvent) {
                skipFirstEvent = false;
                return;
            }
            child_process = cp.spawn("npm", ["run", "build"], { cwd: pluginRootDir, stdio: "inherit" });

            child_process.on('close', (code) => {
                console.log(`Build finished with code: ${code}`);
                child_process = null;
            });
        });
    }

    private static buildUIPlugin(pluginRootDir: string) {
        return new Promise<void>((resolve, reject) => {
            const child_process = cp.spawn("npm", ["run", "build"], { cwd: pluginRootDir, stdio: "inherit" });
            
            child_process.on('close', (code) => {
                console.log(`Build finished with code: ${code}`);
                resolve();
            });
        });
    }
}

export class VcdAuth {

    /**
     * Prompts the user to select particular VCD auth configuration from the store
     * @param vcdAuthFile - path to the VCD authentication store file
     */
    public static async use(vcdAuthFile: string) {

        const configs = CloudDirectorConfig.getConfigurations(vcdAuthFile);

        if (configs.configurations.length <= 0) {
            console.log("No VCD Authentication Configurations stored yet!");
            return;
        }

        const answers = await inquirer.prompt({
            type: 'list',
            name: 'alias',
            message: 'Select configuration',
            default: configs.current,
            loop: false,
            choices: configs.configurations.map((element) => {
                return {
                    name: `${element.key}: ${element.username}/${element.org} ${element.basePath}`,
                    value: element.key
                };
            }),
        });
        CloudDirectorConfig.use(answers.alias, vcdAuthFile);

        return await this.getCloudDirectorConfig(vcdAuthFile);
    }

    /**
     * Returns current VCD Auth configuration
     * @param vcdAuthFile - path to the VCD authentication store file
     */
    public static async getCloudDirectorConfig(vcdAuthFile: string): Promise<CloudDirectorConfig> {
        let config = CloudDirectorConfig.fromFile(vcdAuthFile);
        if (!config.connectionAuth.authorized && config.connectionAuth.authorizationError === 'Token expired') {
            const answers = await inquirer.prompt({
                type: 'password',
                name: 'password',
                message:
                    `Token has expired. Please enter the password for ${config.authentication.username}@${config.authentication.org} again: `,
            });
            const password = answers.password;
            config = await this.loginAndStore(
                CloudDirectorConfig.getConfigurations().current,
                config.basePath, config.authentication.username, config.authentication.org, password, vcdAuthFile);
        }
        return config;
    }

    /**
     * Creates a new VCD Auth configuration and stores it VCD authentication store file
     * @param alias - alias use for storing
     * @param vcdHost - Cloud director host
     * @param user - username
     * @param org - organization
     * @param password - password
     * @param vcdAuthFile - path to the VCD authentication store file
     */
    public static async loginAndStore(alias: string, vcdHost: string, user: string, org: string, password: string, vcdAuthFile: string) {
        const config = await CloudDirectorConfig.withUsernameAndPassword(
            vcdHost,
            user,
            org,
            password
        );
        if (!config.connectionAuth.authorized) {
            console.warn('Connection error: ' + config.connectionAuth.authorizationError);
            console.log(config.connectionAuth.certificate);
            const answers = await inquirer.prompt({
                type: 'confirm',
                name: 'accept',
                message: 'Do you accept the provided certificate?',
            });
            config.connectionAuth.authorized = answers.accept;
        }
        config.saveConfig(alias, vcdAuthFile);
        return config;
    }
}