const Docker = require('dockerode');
const docker = new Docker({
    'socketPath': '/home/docker.sock'
});
const fs = require('fs');
const config = require('qmi-cloud-common/config');
const GIT_SCENARIOS = process.env.GIT_SCENARIOS;
const GIT_TAG = process.env.GIT_TAG || "master";
const DOCKERIMAGE = process.env.DOCKERIMAGE_TERRAFORM || "qlikgear/terraform:1.0.1";
const SSHPATH = process.env.SSHPATH;

function hook_stdout(callback) {
    var old_write = process.stdout.write

    process.stdout.write = (function(write) {
        return function(string, encoding, fd) {
            write.apply(process.stdout, arguments)
            callback(string, encoding, fd)
        }
    })(process.stdout.write)

    return function() {
        process.stdout.write = old_write
    }
}

function _buildVarsExec( exec, provision, scenario ) {

    let prefix = provision.scenario.toUpperCase();

    prefix = prefix.replace(/AZQMI/g, 'QMI');
    exec.push('-var');
    exec.push(`prefix=${prefix}`);

    if ( provision.deployOpts && provision.deployOpts.subsId ) {
        exec.push('-var');
        exec.push(`subscription_id=${provision.deployOpts.subsId}`);
    } 
    //Deprecated
    else if ( scenario && scenario.subscription && scenario.subscription.subsId ) {
        exec.push('-var');
        exec.push(`subscription_id=${scenario.subscription.subsId}`);
    }
    
    if ( provision.deployOpts ) {

        if ( provision.deployOpts.location ) {
            exec.push('-var');
            exec.push(`location=${provision.deployOpts.location}`);
        }

        if ( provision.deployOpts.vnetExists ) {
            exec.push('-var');
            exec.push(`subnet_id=${provision.deployOpts.subnetId}`);

            if ( provision.isExternalAccess ) {
                exec.push('-var');
                exec.push(`app_gw_subnet=${provision.deployOpts.appGwSubnetId}`);
            }
        }
       
    } 
    //Deprecated
    else if ( scenario && scenario.subscription && scenario.subscription.vnetExists ) {
        exec.push('-var');
        exec.push(`subnet_id=${scenario.subscription.subnetId}`);

        if ( scenario.isExternal || provision.isExternalAccess) {
            exec.push('-var');
            exec.push(`app_gw_subnet=${scenario.subscription.appGwSubnetId}`);
        }
    }

    exec.push('-var');
    exec.push(`provision_id=${provision._id}`);
    exec.push('-var');
    exec.push(`user_id=${provision.user.displayName}`);

    if ( provision.scenario.indexOf('azqmi-qdi') !== -1 && provision.version && provision.version >= config.PROVISION_VERSION ) {
        exec.push('-var');
        exec.push(`user_email=${provision.user.upn}`);
    }

    if ( provision.scenario.indexOf('azqmi-synapse') !== -1 || provision.scenario.indexOf('azqmi-qdi') !== -1 ) {
        exec.push('-var');
        exec.push(`user_oid=${provision.user.oid}`);
    }
  
    if (!provision.vmImage) {
        //Old way
        if ( provision.vmType ) {
            exec.push('-var');
            exec.push(`vm_type=${provision.vmType}`);
        }

        if ( provision.nodeCount) {
            exec.push('-var');
            exec.push(`agent_count=${provision.nodeCount}`);
        }
    } else if ( provision.vmImage ) {   
        //New way    
        for ( let key in provision.vmImage ) {
            
            if ( !provision.vmImage[key].disabled ) {

                if ( provision.vmImage[key].nodeCount ) {
                    exec.push('-var');
                    exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
                }

                if ( provision.vmImage[key].vmType ) {
                    exec.push('-var');
                    exec.push(`vm_type_${key}=${provision.vmImage[key].vmType}`);
                } else {
                    exec.push('-var');
                    exec.push(`vm_type_${key}=ENABLED`);
                }

                if ( provision.vmImage[key].diskSizeGb ) {
                    exec.push('-var');
                    exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`);
                }
    
                if ( provision.vmImage[key].version && provision.vmImage[key].version.image) {
                    exec.push('-var');
                    exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`);
                }

                if ( provision.vmImage[key].version && provision.vmImage[key].version.init_password) {
                    exec.push('-var');
                    exec.push(`image_reference_${key}_init_password=${provision.vmImage[key].version.init_password}`);
                }
            }      
        }      
    } 
    
    if ( provision.isExternalAccess ) {
        exec.push('-var');
        exec.push(`is_external_access=${provision.isExternalAccess}`);
    }

    if ( provision.schedule ) {
        if ( provision.schedule.is24x7 === true ) {
            exec.push('-var');
            exec.push(`is_24x7=true`);
        } else if ( provision.schedule.is24x7 === false ) {
            exec.push('-var');
            exec.push(`is_24x7=false`);
            if ( provision.schedule.utcTagStartupTime && provision.schedule.isStartupTimeEnable ) {
                exec.push('-var');
                exec.push(`startupTime=${provision.schedule.utcTagStartupTime}`);
            }
            if ( provision.schedule.utcTagShutdownTime ) {
                exec.push('-var');
                exec.push(`shutdownTime=${provision.schedule.utcTagShutdownTime}`);
            }
        }
    }

    return exec;
}

const init = function( provision ) {
    
    const name = `qmi-tf-init-${provision._id}`;
    console.log(`Terraform# Init: will spin up container: ${name}`);
    var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
    let gitBranch = GIT_TAG;
    if ( provision._scenarioDoc && provision._scenarioDoc.gitBranch && provision._scenarioDoc.gitBranch.trim() !== "") {
        gitBranch = provision._scenarioDoc.gitBranch;
    }
    let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provision.scenario}?ref=${gitBranch}`];
    console.log('Terraform# Init: exec: '+exec.join(" "));
    var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
    console.log('Terraform# Init: version to use is : '+terraformImage);
    return docker.run(terraformImage, exec, processStream, { 
        //"Env": ["VAR_ENV=whatever"],
        "name": name,
        "WorkingDir": "/app",
        "HostConfig": {
            "Binds": [
                `${provision.path}:/app`
            ]
        }
    }).then(function(data) {
        var output = data[0];
        var container = data[1];
        console.log(`Terraform# Init: ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
        return container.remove().then(function(){
            console.log(`Terraform# Init: ${name} removed!`);
            return output.StatusCode;
        });
    }).then(function(statusCode) {
        return {"provision": provision, "statusCode": statusCode};
    });
};

const plan = function( provision, scenario ) {
    
    const name = `qmi-tf-plan-${provision._id}`;
    console.log(`Terraform# Plan: will spin up container: ${name}`);
    var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
    //var processStream = process.stdout;
    var exec = ['terraform', 'plan', '-no-color', '-input=false', '-out=tfplan' ];
    exec = _buildVarsExec(exec, provision, scenario);
    console.log('Terraform# Plan: exec: '+exec.join(" "));
    var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
    console.log('Terraform# Plan: version to use is : '+terraformImage);
    return docker.run(terraformImage, exec, processStream, { 
        //"Env": ["VAR_ENV=whatever"],
        "name": name,
        "WorkingDir": "/app",
        "HostConfig": {
            "Binds": [
                `${provision.path}:/app`,
                `${SSHPATH}:/root/.ssh`
            ],
            "NetworkMode": "host"
        }
    }).then(function(data){
        var container = data[1];
        console.log(`Terraform# Plan: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
        return container.remove().then(function(){
            console.log(`Terraform# Plan: ${name} removed!`);
            return data[0].StatusCode;
        });
    }).then(function(statusCode) {
        return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode};
    })
};

const apply = function( provision ) {
    
    const name = `qmi-tf-apply-${provision._id}`;
    console.log(`Terraform# Apply: will spin up container: ${name}`);
    var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
    //var processStream = process.stdout;

    var exec = ['terraform', 'apply', 'tfplan', '-no-color'];
    console.log('Terraform# Apply: exec: '+exec.join(" "));
    var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
    console.log('Terraform# Apply: version to use is : '+terraformImage);
    return docker.run(terraformImage, exec, processStream, { 
        //"Env": ["VAR_ENV=whatever"],
        "name": name,
        "WorkingDir": "/app",
        "HostConfig": {
            "Binds": [
                `${provision.path}:/app`,
                `${SSHPATH}:/root/.ssh`
            ],
            "NetworkMode": "host"
        }
    }).then(function(data){
        let container = data[1];
        console.log(`Terraform# Apply: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
        return container.remove().then(function(){
            console.log(`Terraform# Apply: Container '${name}' removed!`);
            return data[0].StatusCode;
        });
    }).then(function(statusCode) {
        return  {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode};
    })
}

const destroy = function(destroyMongo, provision, scenario) {

    const name = `qmi-tf-destroy-${destroyMongo._id}`;
    console.log(`Terraform# Destroy: will spin up container: ${name}`);
    var processStream = fs.createWriteStream(destroyMongo.logFile, {flags:'a'});
    var exec = ['terraform', 'destroy', '-auto-approve', '-no-color'];
    exec = _buildVarsExec(exec, provision, scenario);
    console.log('Terraform# Destroy: exec: '+exec.join(" "));

    var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
    console.log('Terraform# Destroy: version to use is : '+terraformImage);

    return docker.run(terraformImage, exec, processStream, { 
        //"Env": ["VAR_ENV=whatever"],
        "name": name,
        "WorkingDir": "/app",
        "HostConfig": {
            "Binds": [
                `${provision.path}:/app`,
                `${SSHPATH}:/root/.ssh`
            ]
        }
    }).then(function(data) {
        var container = data[1];
        console.log(`Terraform# Destroy: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
        return container.remove().then(function(){
            console.log(`Terraform# Destroy: Container '${name}' removed!`);
            return data[0].StatusCode;
        });
    }).then(async function(statusCode) {
        
        return {"output": fs.readFileSync(destroyMongo.logFile), "statusCode": statusCode};
    });
};

const outputs = function(provision) {

    const name = `qmi-tf-output-${provision._id}`;
    console.log(`Terraform# Output: will spin up container: ${name}`);
    

    var exec = ['terraform', 'output', '-no-color', '-json'];
    console.log('Terraform# Output: exec: '+exec.join(" "));

    var tfout = "";
    var unhook = hook_stdout(function(string, encoding, fd) {
        tfout += string.trim();
    });
    
    var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;

    return docker.run(terraformImage, exec, process.stdout, { 
        //"Env": ["VAR_ENV=whatever"],
        "name": name,
        "WorkingDir": "/app",
        "HostConfig": {
            "Binds": [
                `${provision.path}:/app`
            ]
        }
    }).then(function(data) {
        unhook();
        var container = data[1];
        console.log(`Terraform# Output: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
        return container.remove();
    }).then(async function(data) {
        console.log(`Terraform# Output: Container '${name}' removed!`);
        //console.log("Terraform# Output: tfout: " + tfout);
        var out = JSON.parse(tfout);
        var o = {};
        for (var key in out) {
            o[key] = out[key].value;
        }
        return o;
    });
};

const stop = function(provision) {

    if ( provision.status !== "provisioning" ) {
        console.log(`Terraform# Stop: provision (${provision._id}) is not provisioning`);
        return {"message": `Won't stop container: provision (${provision._id}) is not provisioning`};
    }

    const name = `qmi-tf-apply-${provision._id}`;

    console.log(`Terraform# Stop: will try to stop container: ${name}`);

    return docker.listContainers().then(function(containers) {

        var containerID;

        for (let i=0;i<containers.length;i++) {
            if ( containers[i].Names && containers[i].Names.length ) { 
                //console.log(`Terraform# Stop: debug container Names: ${containers[i].Names[0]}`);
                if ( containers[i].Names[0] === ("/"+name) ) {
                    console.log(`Terraform# Stop: container found: ${name}`);
                    containerID = containers[i].Id;  
                    break;
                }
            }
        }   
        if ( containerID ) {
            console.log(`Terraform# Stop: stopping container: ${name} with ID ${containerID}`);
            return docker.getContainer(containerID).kill().then(function(){
                console.log(`Terraform# Stop: container stopped!!: ${name}`);
                return {"message": `container ${name} stopped`}
            });
        } else {
            console.log(`Terraform# Stop: container ${name} not found!`);
            return {"message": `container ${name} not found`};
        }
    });
   
}

module.exports.init = init;
module.exports.plan = plan;
module.exports.apply = apply;
module.exports.destroy = destroy;
module.exports.outputs = outputs;
module.exports.stop = stop;