import { camelize, isBase64 } from './../utils/tools';
import commandLineUsage from 'command-line-usage';
import meow, { AnyFlags } from 'meow';
import { Merge, JsonObject } from 'type-fest';
import { HELP_HEADER } from "./logo";
import { readJsonSync, writeFile } from 'fs-extra'
import * as changeCase from "change-case";
import { tryOpenFileAsObject } from './file-utils';
import { ConfigObject } from '../api/model/config';
import uuidAPIKey from 'uuid-apikey';
import { ev, Spin } from '../controllers/events';
import isUrl from 'is-url-superb';
import * as path from 'path';
import * as fs from 'fs';
import { log, setupLogging } from '../logging/logging';
import { optionList } from './cli-options';

let checkUrl = url => typeof url === 'string' ? isUrl(url) : false;

const configWithCases = readJsonSync(path.join(__dirname,'../../bin/config-schema.json'));

export const optionKeys = optionList.map(({ name }) => camelize(name));

export const optionKeysWithDefalts = [...optionList.filter(o=>o.hasOwnProperty('default')).map(({ name }) => camelize(name)), 'popup'];

export const PrimitiveConverter = {
    Number : 1,
    Boolean : true,
    String : "hello"
}

export const cliOptionNames = optionList.reduce((acc, c) => {
    if (!c.type) return acc
    acc[camelize(c.name)] = typeof PrimitiveConverter[c.type.name]
    return acc
}, {})

export const meowFlags: () => AnyFlags = () => {
    const extraFlags = {}
    configWithCases.map(({ type, key }) => {
        if (key === "popup") type = "number";
        if (key === "viewport") type = "string";
        if (key === "stickerServerEndpoint") type = "string";
        extraFlags[key] = {
            type
        }
    });

    const res = {};
    optionList.map(option => {
        res[camelize(option.name)] = {
            ...option,
            //@ts-ignore
            type: option.type?.name?.toLowerCase()
        }
    });
    return {
        ...res,
        ...extraFlags
    };
}

export const helptext = commandLineUsage([{
    content: HELP_HEADER,
    raw: true,
},
{
    header: '',
    optionList
},
{
    header: "Session config flags",
    optionList: [
        ...configWithCases.map(c => {
            let type;
            if (c.type === 'boolean') type = Boolean;
            if (c.type === 'string') type = String;
            if (c.type === '"number"' || c.type === 'number') type = Number;
            return {
                name: c.p,
                type,
                description: c.description
            }
        })
    ]
},
{
    content: `Please check here for more information on some of the above mentioned parameters: {underline https://docs.openwa.dev/docs/reference/api/model/config/interfaces/ConfigObject}`
},
{
    content: 'Project home: {underline https://github.com/open-wa/wa-automate-nodejs}'
}
])

export const envArgs: () => JsonObject = () => {
    const env = {};
    Object.entries(process.env).filter(([k, ]) => k.includes('WA')).map(([k, v]) => env[changeCase.camelCase(k.replace('WA_', ''))] = (v == 'false' || v == 'FALSE') ? false : (v == 'true' || v == 'TRUE') ? true : Number(v) || v);
    return env
}

export const configFile: (config ?: string) => Promise<JsonObject> = async (config ?: string) => {
    let confFile = null;
    const conf = config || process.env.WA_CLI_CONFIG
    //check if it is a directory:
    const isDir = fs.existsSync(conf) && fs.lstatSync(conf).isDirectory();
    log.info(`Config ${config} is directory: ${isDir}`)
    const backup = async () => {
        if(!confFile) confFile = await tryOpenFileAsObject(`cli.config.json`);
        if(!confFile) confFile = await tryOpenFileAsObject(`cli.config.js`);
    }
    const attempt = async (firstAttempt ?: string, skipBackup ?: boolean) => {
        try {
            if(!confFile) confFile = await tryOpenFileAsObject(firstAttempt || `cli.config.json`);
            if(!skipBackup) await backup();
        } catch (error) {
            log.error(error)
            log.error("Trying cli.config.js")
            await backup();
        }
    }
    if (conf) {
        if (isBase64(conf as string)) {
            confFile = JSON.parse(Buffer.from(conf as string, 'base64').toString('ascii'))
        } else {
            if(isDir) {
                await attempt(`${isDir && conf as string}/cli.config.json`, true);
                await attempt(`${isDir && conf as string}/cli.config.js`, true);
                await backup();
            } else await attempt(conf as string)
            if (!confFile) console.error(`Unable to read config file json: ${conf}`)
        }
    } else {
        await attempt()
    }
    log.info(`Using config file: ${(confFile as any || {}).confPath || "???"}`)
    return confFile || {};
}

export const cli: () => Promise<{
    createConfig: ConfigObject,
    cliConfig: Merge<ConfigObject, {
        [k: string]: any
    }>,
    PORT: number,
    spinner: Spin
}> = async () => {
    let loggingSetup = false;
    const _cli = meow(helptext, {
        flags: {
            ...meowFlags(),
            popup: {
                type: 'boolean',
                default: false
            }
        },
        booleanDefault: undefined
    });

    process.env.CURRENT_SESSION_ID = (_cli.flags?.sessionId as string) || process.env.WA_SESSION_ID || 'session'

    const _setupLogging = (_config : any) => {
        if(loggingSetup) return;
        //firstly set up logger
        if(_config?.logging || _config?.verbose){
            if(!_config?.logging && _config?.verbose) _config.logging = []
            if(_config?.logging && !(_config?.logging || []).find(transport => transport.type === "console")) _config.logging.push({type: 'console'})
            if(Array.isArray(_config?.logging))
            _config.logging = setupLogging(_config?.logging, `easy-api-${_config?.sessionId || 'session'}`)
            loggingSetup = true;
        }
        if(_config.verbose) _config.disableSpins = true;
    }

    _setupLogging(_cli.flags)

    /**
     * Config order should follow airmanship rules. Least maneuverable to most maneuverable.
     * 
     * 1. ENV VARS
     * 2. Config file
     * 3. CLI flags
     */

    const resolvedConfigFromFile =  (await configFile(_cli.flags.config as string) || {})

    const nonCliConfigs = {
        /**
         * Environmental Variables
         */
         ...envArgs(),
         /**
          * The configuration file OR the --config base64 encoded config object
          */
         ...resolvedConfigFromFile,
    }

    optionList.filter(option=>option.default)

    const cliConfig: any = {
        sessionId: "session",
        /**
         * Prioirity goes from bottom up
         */
        ...nonCliConfigs,
        /**
         * CLI flags
         */
        ..._cli.flags,
        /**
         * Grab the configs for the cli defaults
         */
         ...optionKeysWithDefalts.reduce((p,c)=> nonCliConfigs.hasOwnProperty(c) ? {
            ...p,
            [c]:nonCliConfigs[c]
            } : p,{})
    };
    
    //firstly set up logger
    _setupLogging(cliConfig);

    process.env.CURRENT_SESSION_ID = cliConfig.sessionId

    const PORT = Number((typeof cliConfig.forcePort === "boolean" && cliConfig.forcePort ? process.env.PORT : cliConfig.forcePort) || cliConfig.port || process.env.PORT || 8080);
    const spinner = new Spin(cliConfig.sessionId, 'STARTUP', cliConfig?.disableSpins);

    const createConfig: ConfigObject = {
        ...cliConfig
    };


    if (cliConfig?.session) {
        createConfig.sessionData = cliConfig.session;
    }

    if (cliConfig?.allowSessionDataWh) {
        cliConfig.allowSessionDataWebhook = cliConfig.allowSessionDataWh;
    }

    /**
     * Build create() specific conig
     */

    if ((cliConfig?.licenseKey || cliConfig?.l)) {
        createConfig.licenseKey = cliConfig.licenseKey || cliConfig.l
    }

    if (cliConfig?.popup) {
        createConfig.popup = PORT
    }

    if (!(cliConfig?.key == null) && cliConfig?.key == "") {
        cliConfig.key = uuidAPIKey.create().apiKey;
    }

    if (cliConfig.viewport && cliConfig.viewport.split && cliConfig.viewport.split('x').length && cliConfig.viewport.split('x').length == 2 && cliConfig.viewport.split('x').map(Number).map(n => !!n ? n : null).filter(n => n).length == 2) {
        const [width, height] = cliConfig.viewport.split('x').map(Number).map(n => !!n ? n : null).filter(n => n);
        createConfig.viewport = { width, height }
    }

    if (cliConfig.resizable) {
        createConfig.defaultViewport = null // <= set this to have viewport emulation off
    }

    if (cliConfig.sessionDataOnly) {
        ev.on(`sessionData.**`, async (sessionData, sessionId) => {
            writeFile(`${sessionId}.data.json`, JSON.stringify(sessionData), (err) => {
                if (err) { spinner.fail(err.message); return; }
                else
                    spinner.succeed(`Session data saved: ${sessionId}.data.json\nClosing.`);
                process.exit();
            });
        })
    }

    if(cliConfig.skipUrlCheck) checkUrl = () => true;

    if (cliConfig.webhook || cliConfig.webhook == '') {
        if (checkUrl(cliConfig.webhook) || Array.isArray(cliConfig.webhook)) {
            spinner.succeed('webhooks set already')
        } else {
            if (cliConfig.webhook == '') cliConfig.webhook = 'webhooks.json';
            cliConfig.webhook = tryOpenFileAsObject(cliConfig.webhook, true);
            if (!checkUrl(cliConfig.webhook)) {
                cliConfig.webhook = undefined
            }
        }
    }


    if (cliConfig.twilioWebhook || cliConfig.twilioWebhook == '') {
            if (cliConfig.twilioWebhook == '' && cliConfig.webhook) cliConfig.twilioWebhook = cliConfig.webhook;
            if (!checkUrl(cliConfig.twilioWebhook)) {
                cliConfig.twilioWebhook = undefined
        }
        if(cliConfig.twilioWebhook && (!createConfig.cloudUploadOptions || createConfig.messagePreprocessor!=='UPLOAD_CLOUD')) {
            spinner.info('twilioWebhook set but messagePreprocessor not set to UPLOAD_CLOUD or cloudUploadOptions is missing')
        }
    }

    if (cliConfig.apiHost) {
        cliConfig.apiHost = cliConfig.apiHost.replace(/\/$/, '')
    }

    /**
     * Check the port in the config
     */
     cliConfig.port = PORT;

    if (cliConfig.debug) {
        spinner.succeed(`DEBUG - PORT: ${PORT}`)
        spinner.succeed(`DEBUG - flags: ${JSON.stringify(cliConfig)}`)
        const WA_ENV = {};
        Object.keys(process.env).map(k => {
            if (k.startsWith('WA_')) WA_ENV[k] = process.env[k];
        })
        spinner.succeed(`DEBUG - env vars: ${JSON.stringify(WA_ENV)}`)
    }


    return {
        createConfig, cliConfig, PORT, spinner
    }
}