var util = require('util');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var GitRemoteResolver = require('./GitRemoteResolver');
var download = require('../../util/download');
var extract = require('../../util/extract');
var createError = require('../../util/createError');

function GitHubResolver(decEndpoint, config, logger) {
    var pair;

    GitRemoteResolver.call(this, decEndpoint, config, logger);

    // Grab the org/repo
    // /xxxxx/yyyyy.git or :xxxxx/yyyyy.git (.git is optional)
    pair = GitHubResolver.getOrgRepoPair(this._source);
    if (!pair) {
        throw createError('Invalid GitHub URL', 'EINVEND', {
            details: this._source + ' does not seem to be a valid GitHub URL'
        });
    }

    this._org = pair.org;
    this._repo = pair.repo;

    // Ensure trailing for all protocols
    if (!mout.string.endsWith(this._source, '.git')) {
        this._source += '.git';
    }

    // Check if it's public
    this._public = mout.string.startsWith(this._source, 'git://');

    // Use https:// rather than git:// if on a proxy
    if (this._config.proxy || this._config.httpsProxy) {
        this._source = this._source.replace('git://', 'https://');
    }

    // Enable shallow clones for GitHub repos
    this._shallowClone = function() {
        return Q.resolve(true);
    };
}

util.inherits(GitHubResolver, GitRemoteResolver);
mout.object.mixIn(GitHubResolver, GitRemoteResolver);

// -----------------

GitHubResolver.prototype._checkout = function () {
    // Only fully works with public repositories and tags
    // Could work with https/ssh protocol but not with 100% certainty
    if (!this._public || !this._resolution.tag) {
        return GitRemoteResolver.prototype._checkout.call(this);
    }

    var msg;
    var tarballUrl = 'https://github.com/' + this._org + '/' + this._repo + '/archive/' + this._resolution.tag + '.tar.gz';
    var file = path.join(this._tempDir, 'archive.tar.gz');
    var reqHeaders = {};
    var that = this;

    if (this._config.userAgent) {
        reqHeaders['User-Agent'] = this._config.userAgent;
    }

    this._logger.action('download', tarballUrl, {
        url: that._source,
        to: file
    });

    // Download tarball
    return download(tarballUrl, file, {
        ca: this._config.ca.default,
        strictSSL: this._config.strictSsl,
        timeout: this._config.timeout,
        headers: reqHeaders
    })
    .progress(function (state) {
        // Retry?
        if (state.retry) {
            msg = 'Download of ' + tarballUrl + ' failed with ' + state.error.code + ', ';
            msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
            that._logger.debug('error', state.error.message, { error: state.error });
            return that._logger.warn('retry', msg);
        }

        // Progress
        msg = 'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
        if (state.total) {
            msg += ' of ' + (state.total / 1024 / 1024).toFixed(1) + 'MB downloaded, ';
            msg += state.percent + '%';
        }
        that._logger.info('progress', msg);
    })
    .then(function () {
        // Extract archive
        that._logger.action('extract', path.basename(file), {
            archive: file,
            to: that._tempDir
        });

        return extract(file, that._tempDir)
        // Fallback to standard git clone if extraction failed
        .fail(function (err) {
            msg =  'Decompression of ' + path.basename(file) + ' failed' + (err.code ? ' with ' + err.code : '') + ', ';
            msg += 'trying with git..';
            that._logger.debug('error', err.message, { error: err });
            that._logger.warn('retry', msg);

            return that._cleanTempDir()
            .then(GitRemoteResolver.prototype._checkout.bind(that));
        });
    // Fallback to standard git clone if download failed
    }, function (err) {
        msg = 'Download of ' + tarballUrl + ' failed' + (err.code ? ' with ' + err.code : '') + ', ';
        msg += 'trying with git..';
        that._logger.debug('error', err.message, { error: err });
        that._logger.warn('retry', msg);

        return that._cleanTempDir()
        .then(GitRemoteResolver.prototype._checkout.bind(that));

    });
};

GitHubResolver.prototype._savePkgMeta = function (meta) {
    // Set homepage if not defined
    if (!meta.homepage) {
        meta.homepage = 'https://github.com/' + this._org + '/' + this._repo;
    }

    return GitRemoteResolver.prototype._savePkgMeta.call(this, meta);
};

// ----------------

GitHubResolver.getOrgRepoPair = function (url) {
    var match;

    match = url.match(/(?:@|:\/\/)github.com[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i);
    if (!match) {
        return null;
    }

    return {
        org: match[1],
        repo: match[2]
    };
};

module.exports = GitHubResolver;
