import _ from "the-lodash";
import { Promise } from "the-promise";
import { ILogger } from "the-logger";
import { KubernetesClient, KubernetesClientConfig } from "k8s-super-client";
import { K8sLoader, ReadyHandler } from "./k8s";
import { promises as fs } from "fs";
import { basename } from "path";
import { decode } from "base-64";
import { exec } from "child_process";
import { Context } from "../context";
import { Options } from "../../lib/types";

export class RemoteLoader {
    private _readyHandler?: ReadyHandler;
    private _context: Context;
    private _logger: ILogger;
    private _loader: any;
    private _config: any;
    private _errorMessages: string[];
    constructor(context: Context, config: any) {
        this._context = context;
        this._logger = context.logger.sublogger("RemoteLoader");
        this._loader = null;
        this._config = config;
        this._errorMessages = [];
        this.logger.info("Constructed");
    }

    get logger() {
        return this._logger;
    }

    get errorMessages() {
        return this._errorMessages;
    }

    setupReadyHandler(handler: ReadyHandler) {
        this._readyHandler = handler;
        if (this._loader) {
            this._loader.setupReadyHandler(this._readyHandler);
        }
    }

    run() {
    this.logger.info("[run] ", this._config);

    this._errorMessages = [];

    let k8sConfig: KubernetesClientConfig = {
        // server: null,
        // token: null,
        httpAgent: {},
    };

    return Promise.resolve()
        .then(() => this._setup())
        .then(() => this._fetchEndpoint())
        .then((result) => {
            k8sConfig.server = result;
        })
        .then(() => this._fetchCertificateAuthority())
        .then((result) => {
            if (result) {
                k8sConfig.httpAgent!.ca = result;
            }
        })
        .then(() => this._fetchClientCertificate())
        .then((result) => {
            if (result) {
                k8sConfig.httpAgent!.cert = result;
            }
        })
        .then(() => this._fetchClientKey())
        .then((result) => {
            if (result) {
                k8sConfig.httpAgent!.key = result;
            }
        })
        .then(() => this._fetchToken())
        .then((result) => {
            if (result) {
                k8sConfig.token = result;
            }
        })
        .then(() => this._finalizeSetup(k8sConfig))
        .then(() => {
            if (this._errorMessages.length > 0) {
                throw {
                messages: this._errorMessages,
                };
            }
            return this._tryConnect(k8sConfig);
        });
    }

    stop() {
        if (this._loader) {
            this._loader.stop();
            this._loader = null;
        }
    }

    private _tryConnect(k8sConfig: KubernetesClientConfig) {
        this.logger.info("[_tryConnect] config: ", k8sConfig);
        const client = new KubernetesClient(this._logger, k8sConfig);
        return client.init().then(() => {
            let info = {
            infra: "local",
            };
            this._loader = new K8sLoader(this._context, client, info);
            return this._loader.run();
        });
    }

    private _setup() {
        this.logger.info("[_setup] Config: ", this._config);
        return Promise.resolve();
    }

    private _fetchEndpoint() {
        return this._config.cluster.server;
    }

    private _fetchCertificateAuthority() {
        if (this._config.cluster["certificate-authority-data"]) {
            let data = this._config.cluster["certificate-authority-data"];
            return decode(data);
        }
        if (this._config.cluster["certificate-authority"]) {
            let filePath = this._config.cluster["certificate-authority"];
            return this._readFromFile(filePath);
        }
        return null;
    }

    private _fetchToken() {
        if (this._config.user.token) {
            return this._config.user.token;
        }

        if (this._config.user.exec) {
            if (this._config.user.exec.command) {
            return this._executeTool(
                this._config.user.exec.command,
                this._config.user.exec.args,
                this._config.user.exec.env
            ).then((result) => {
                let doc = JSON.parse(result);
                return doc.status.token;
            });
            }
        }

        if (this._config.user["auth-provider"]) {
            if (this._config.user["auth-provider"]["config"]) {
            let authConfig = this._config.user["auth-provider"]["config"];
            if (authConfig["cmd-path"]) {
                return this._executeTool(
                authConfig["cmd-path"],
                authConfig["cmd-args"]
                ).then((result) => {
                let doc = JSON.parse(result);
                let tokenKey = authConfig["token-key"];
                tokenKey = _.trim(tokenKey, "{}.");
                let token = _.get(doc, tokenKey);
                return token;
                });
            }

            if (authConfig["access-token"]) {
                return authConfig["access-token"];
            }
            }
        }
    }

    private _fetchClientCertificate() {
        if (this._config.user["client-certificate-data"]) {
            let data = this._config.user["client-certificate-data"];
            return decode(data);
        }
        if (this._config.user["client-certificate"]) {
            let filePath = this._config.user["client-certificate"];
            return this._readFromFile(filePath);
        }
        return null;
    }

    private _fetchClientKey() {
        if (this._config.user["client-key-data"]) {
            let data = this._config.user["client-key-data"];
            return decode(data);
        }
        if (this._config.user["client-key"]) {
            let filePath = this._config.user["client-key"];
            return this._readFromFile(filePath);
        }
        return null;
    }

    private _readFromFile(filePath: string) {
        this.logger.info("Loading from: %s", filePath);
        return fs.readFile(filePath, "utf8").catch((reason) => {
            this.logger.error("Failed to load from: %s. Details: ", filePath, reason);
            this._errorMessages.push("Failed to load from: " + filePath);
            return null;
        });
    }

    private _finalizeSetup(k8sConfig: KubernetesClientConfig) {
        if (this._config.cluster["insecure-skip-tls-verify"]) {
            k8sConfig.httpAgent!.rejectUnauthorized = false;
        }

        if (this._config.hostOverride) {
            k8sConfig.httpAgent!.servername = this._config.hostOverride;
        }
    }

    private _executeTool(toolPath: string, args: string, envArray?: [])
    {
        let toolName;
        if (this._config.toolMappings[toolPath]) {
            toolName = this._config.toolMappings[toolPath];
        } else {
            toolName = basename(toolPath);
        }

        let envDict = {};
        if (envArray) {
            envDict = _.makeDict(
            envArray,
            (x) => x.name,
            (x) => x.value
            );
        }
        return this._executeCommand(toolName, args, envDict);
    }

    private _executeCommand(
            program: string,
            args: string,
            envDict?: {}
        ) : Promise<string>
    {
        let options: Options = {};
        options.timeout = 20 * 1000;
        if (_.isArray(args)) {
            args = args.join(" ");
        }
        let cmd = program;
        if (args && args.length > 0) {
            cmd = program + " " + args;
        }
        if (envDict) {
            envDict = _.defaults(envDict, process.env);
            options.env = envDict;
        }

        this.logger.info("[_executeCommand] running: %s, options:", cmd, options);
        return Promise.construct((resolve, reject) => {
            exec(cmd, options, (error, stdout, stderr) => {
            if (error) {
                this.logger.error("[_executeCommand] failed: %s", error.message);
                this.logger.error("[_executeCommand] cmd: %s", error.cmd);
                this.logger.error("[_executeCommand] killed: %s", error.killed);
                this.logger.error("[_executeCommand] signal: %s", error.signal);
                this.logger.error("[_executeCommand] code: %s", error.code);
                this.logger.error("[_executeCommand] stdout: %s", stdout);
                this.logger.error("[_executeCommand] stderr: %s", stderr);
                reject(error);
            } else {
                this.logger.info("[_executeCommand] result: ", stdout);
                resolve(stdout);
            }
            });
        });
    }
}