// LICENSE_CODE ZON
'use strict'; /*jslint node:true, es6:false*/
var zconf = require('../../util/config.js');
var date = require('../../util/date.js');
var etask = require('../../util/etask.js');
var wget = require('../../util/wget.js');
var zescape = require('../../util/escape.js');
var zerr = require('../../util/zerr.js');
var crypto = require('crypto');
var E = module.exports = agent_proxy, assign = Object.assign;
var AGENT_TTL = 5*date.ms.MIN;
var uuid, session_key, jwt;
var paid_mode = false;
var product;
E.jwt_conf = {};

function agent_proxy(country){
    this.country = country;
    this.server = null;
    this.ip = null;
    this.port = null;
    this.credentials = null;
    this.basic = null;
    this.ts = null;
    this.sp = null;
    this.jwt = E.jwt_conf;
    this.pending = [];
    this.exclude = [];
}

// XXX: replace with some kind of config variable when available
// (currently, the smart proxy only runs on Android)
E.version_header = zconf.ZON_VERSION+' android';

E.set_auth_info = function(_uuid, _session_key, _jwt){
    zerr.notice('set_auth_info '+_uuid+' '+_session_key+' '+_jwt);
    uuid = _uuid;
    session_key = _session_key;
    E.jwt_conf.jwt = _jwt;
};

E.set_paid_mode = function(mode){ paid_mode = mode; };

E.set_product = function(prod){ product = prod; };

E.prototype.use = function(){
    var _this = this;
    if (this.server && this.ts && Date.now()-this.ts<=AGENT_TTL && !this.sp)
        return;
    return etask('agent_proxy.use', [function(){
        if (_this.sp)
        {
            _this.pending.push(this);
            return this.wait();
        }
        return _this.find_agent();
    }]);
};

var sh256_hmac = function(uuid, msg){
    uuid = uuid.replace(/^apk[-p]*/, '').toLowerCase();
    var hmac = crypto.createHmac('sha256', uuid);
    hmac.update(msg);
    return hmac.digest('hex');
};

E.prototype.find_agent = function(){
    var country = this.country.toLowerCase();
    var server = this.server, ip = this.ip, port = this.port;
    var credentials = this.credentials;
    var _this = this;
    return this.sp = etask('agent_proxy.get_agent', [function(){
        if (server)
            return this.goto('verify');
    }, function loop(){
        var params = {
            country: country,
            uuid: uuid,
            session_key: session_key,
            exclude: _this.exclude.length ? _this.exclude.join(',') :
                undefined,
        };
        if (paid_mode)
            assign(params, {identity: sh256_hmac(uuid, 'true')});
        if (product)
            params.product = product;
        return wget(zescape.uri(
            'http://client.hola.org/client_cgi/zgettunnels', params),
            {expect: 200, json: true, slow: 0});
    }, function(res){
        zerr.notice('zgettunnels: '+JSON.stringify(res.body));
        credentials = 'user-uuid-'+uuid+':'+(paid_mode ?
            sh256_hmac(uuid, 'true') : res.body.agent_key);
        var proxy = res.body.ztun[country][0];
        if (!proxy)
            throw new Error('No servers available');
        var m = /^HTTP ([^:]+):(\d+)$/.exec(proxy);
        server = m[1];
        ip = res.body.ip_list[server];
        port = +m[2];
    }, function catch$(err){
        _this.server = _this.ip = _this.port = null;
        _this.credentials = _this.basic = null;
        zerr.err('No agents for '+_this.country+': '+err);
        for (var i in _this.pending)
            _this.pending[i].throw(err);
        _this.pending = [];
        return this.throw(err);
    }, function verify(){
        zerr.info('Verifying '+server+':'+port+' for '+_this.country);
        var headers = {'x-hola-version': E.version_header};
        if (_this.jwt.jwt)
            headers['x-hola-jwt'] = _this.jwt.jwt;
        return wget('http://hola.org/myip.json', {
            expect: 200,
            json: true,
            slow: 0,
            proxy: 'http://'+credentials+'@'+ip+':'+port,
            headers: headers,
        });
    }, function(res){
        if (this.error || !country.startsWith(res.body.country.toLowerCase()))
        {
            zerr.err(server+':'+port+': '
                +(this.error || 'wrong country '+res.body.country));
            _this.exclude.push(server);
            return this.goto('loop');
        }
        zerr.info(server+':'+port+' verified for '+_this.country);
        _this.server = server;
        _this.ip = ip;
        _this.port = port;
        _this.credentials = credentials;
        _this.basic = 'Basic '+new Buffer(credentials).toString('base64');
        _this.ts = Date.now();
        for (var i in _this.pending)
            _this.pending[i].continue();
        _this.pending = [];
    }, function finally$(){
        _this.sp = null;
    }]);
};

E.prototype.report_response = function(code){
    if (code>=200 && code<300 || code==304)
        this.ts = Date.now();
};

E.prototype.report_error = function(){
    this.ts = null; // force agent verification
    this.use();
};

E.prototype.probe = function(url){
    var _this = this;
    return etask('agent_proxy.probe', [function(){
        return _this.use();
    }, function(){
        return wget(zescape.uri('http://'+_this.ip+':'+_this.port+'/probe',
            {url: url.orig, country: _this.country}),
            {expect: 200, json: true, slow: 0});
    }, function(res){
        return res.body.type;
    }]);
};
