var http  = require("http"),
    https          = require("https"),
    net            = require("net"),
    fs             = require("fs"),
    url            = require("url"),
    pathUtil       = require("path"),
    color          = require("colorful"),
    sleep          = require("sleep"),
    Buffer         = require('buffer').Buffer,
    httpsServerMgr = require("./httpsServerMgr");

var httpsServerMgrInstance = new httpsServerMgr();

//default rule
var handleRule = {
    map :[
        // {
        //     host      :".",
        //     path      :"/path/test",
        //     localFile :"",
        //     localDir  :"~/"
        // }
    ]
    ,httpsConfig:{
        bypassAll : true,
        interceptDomains:["^.*alibaba-inc\.com$"]
    }
};

function userRequestHandler(req,userRes){
    var host = req.headers.host,
        urlPattern         = url.parse(req.url),
        path               = urlPattern.path,
        ifLocalruleMatched = false,
        callback           = null,
        ifHttps            = !!req.connection.encrypted && !/http:/.test(req.url),
        resourceInfo       = {},
        resourceInfoId     = -1;

    resourceInfo.host      = host;
    resourceInfo.method    = req.method;
    resourceInfo.path      = path;
    resourceInfo.url       = (ifHttps ? "https://" :"http://") + host + path;
    resourceInfo.req       = req;
    resourceInfo.startTime = new Date().getTime();

    try{
        resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo);
    }catch(e){}

    console.log(color.green("\nreceived request to : " + host + path));
    /*
        req.url is wired
        in http  server : http://www.baidu.com/a/b/c
        in https server : /work/alibaba
    */

    //handle OPTIONS request
    if(req.method == "OPTIONS"){
        console.log("==>OPTIONS req for CROS, will allow all");
        userRes.writeHead(200,mergeCORSHeader(req.headers)); //remove any cache related header, add crossdomain headers
        userRes.end();
        return;
    }

    //try to mactch rule file
    for(var index in handleRule.map){
        var rule = handleRule.map[index];


        var hostTest = new RegExp(rule.host).test(host),
            pathTest = new RegExp(rule.path).test(path);

        if(hostTest && pathTest && (rule.localFile || rule.localDir) ){
            console.log("==>meet the rules, will map to local file");

            var targetLocalfile = rule.localFile;

            //localfile not set, map to dir
            if(!targetLocalfile){ //find file in dir, /a/b/file.html -> dir + b/file.html
                var remotePathWithoutPrefix = path.replace(new RegExp(rule.path),""); //remove prefix
                targetLocalfile = pathUtil.join(rule.localDir,remotePathWithoutPrefix);
            }

            console.log("==>local file: " + targetLocalfile);
            if(fs.existsSync(targetLocalfile)){
                try{
                    var fsStream = fs.createReadStream(targetLocalfile);
                    userRes.writeHead(200,mergeCORSHeader( req.headers,{}) ); //CORS for localfiles
                    fsStream.pipe(userRes);
                    ifLocalruleMatched = true;
                    break;
                }catch(e){
                    console.log(e.message);
                }
            }else{
                console.log("file not exist : " + targetLocalfile);
            }
        }

        //sleep for seconds if configed in the rule file
        //see rule_sample.js
        if(hostTest && pathTest && !!rule.sleep){
            console.log(color.yellow('[' + req.url + '] will sleep for ' + rule.sleep + ' seconds.'));
            sleep.sleep(rule.sleep);
        }

        if(hostTest && pathTest && !!rule.callback){
            callback = rule.callback;
        }
    }

    if(ifLocalruleMatched){
        return;

    }else{
        console.log("==>will forward to real server by proxy");

        var options = {
            hostname : urlPattern.hostname || req.headers.host,
            port     : urlPattern.port || req.port || (ifHttps ? 443 : 80),
            path     : path,
            method   : req.method,
            headers  : req.headers
        };

        var proxyReq = (ifHttps ? https : http).request(options, function(res) {
            userRes.writeHead(res.statusCode,mergeCORSHeader(req.headers,res.headers));

            var resData = [],
                length  = 0;
            res.on("data",function(chunk){
                resData.push(chunk);
                length  += chunk.length;
                userRes.write(chunk);
            });

            res.on("end",function(){
                callback && callback.call(null,userRes);
                userRes.end();

                //update record info
                resourceInfo.endTime = new Date().getTime();
                resourceInfo.res     = res;
                resourceInfo.resBody = Buffer.concat(resData);
                resourceInfo.length  = length;

                try{
                    GLOBAL.recorder.updateRecord(resourceInfoId,resourceInfo);
                }catch(e){}
                
            });
            res.on('error',function(error){
                console.log('error',error);
            });

        });

        proxyReq.on("error",function(e){
            console.log("err with request :" + e.code + "  " + req.url);
            userRes.end();
        });

        req.pipe(proxyReq);
    }
}

function connectReqHandler(req, socket, head){
    var hostname  = req.url.split(":")[0],
        targetPort= req.url.split(":")[1],
        httpsRule = handleRule.httpsConfig;

    var shouldBypass = !!httpsRule.bypassAll;
    if(!shouldBypass){ //read rules
        shouldBypass = true;
        for(var index in httpsRule.interceptDomains){
            var reg = new RegExp(httpsRule.interceptDomains[index]);
            if( reg.test(hostname) ){
                shouldBypass = false;
                break;
            }
        }
    }

    console.log(color.green("\nreceived https CONNECT request " + hostname));

    if(shouldBypass){
        console.log("==>will bypass the man-in-the-middle proxy");
        try{
            var conn = net.connect(targetPort, hostname, function(){
                socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', function() {
                    conn.pipe(socket);
                    socket.pipe(conn);
                });
            }); 

            conn.on("error",function(e){
                console.log("err when connect to __host".replace(/__host/,hostname));
            });  
        }catch(e){
            console.log("err when connect to remote https server (__hostname)".replace(/__hostname/,hostname));//TODO 
        }

    }else{
        //TODO : remote port other than 433
        console.log("==>meet the rules, will forward to local https server");

        //forward the https-request to local https server
        httpsServerMgrInstance.fetchPort(hostname,userRequestHandler,function(err,port){
            if(!err && port){
                try{
                    var conn = net.connect(port, 'localhost', function(){ //TODO : localhost -> server
                        socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'UTF-8', function() {
                            conn.pipe(socket);
                            socket.pipe(conn);
                        });
                    });   

                    conn.on("error",function(e){
                        console.log("err when connect to __host".replace(/__host/,hostname));
                    });
                }catch(e){
                    console.log("err when connect to local https server (__hostname)".replace(/__hostname/,hostname));//TODO 
                }
                
            }else{
                console.log("err fetch HTTPS server for host:" + hostname);
            }
        });        
    }
}

function setRules(newRule){
    if(!newRule){
        return;
    }

    if(!newRule.map || !newRule.httpsConfig){
        throw(new Error("invalid rule schema"));
    }else{
        handleRule = newRule;
    }
}

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
function mergeCORSHeader(reqHeader,originHeader){
    var targetObj = originHeader || {};

    delete targetObj["Access-Control-Allow-Credentials"];
    delete targetObj["Access-Control-Allow-Origin"];
    delete targetObj["Access-Control-Allow-Methods"];
    delete targetObj["Access-Control-Allow-Headers"];

    targetObj["access-control-allow-credentials"] = "true";
    targetObj["access-control-allow-origin"]      = reqHeader['origin'] || "-___-||";
    targetObj["access-control-allow-methods"]     = "GET, POST, PUT";
    targetObj["access-control-allow-headers"]     = reqHeader['access-control-request-headers'] || "-___-||";

    //
    // targetObj["Transfer-Encoding"]                = "chunked";

    // // Disable caching
    // // If the response status is 304 not modified, the data event of response will not emmit
    // targetObj["Cache-Control"]                    = "no-cache, no-store, must-revalidate";
    // targetObj["Pragma"]                           = "no-cache";
    // targetObj["Expires"]                          = 0;
    // //
    // targetObj["server"]                           = "anyproxy server";
    // targetObj["x-powered-by"]                     = "Alipay-ct-wd";

    return targetObj;
}

module.exports.userRequestHandler = userRequestHandler;
module.exports.connectReqHandler  = connectReqHandler;
module.exports.setRules           = setRules;