var fs = require('fs');
var path = require('path');
var util = require('util');
var mkpath = require('mkpath');
var Logger = require('../util/logger.js');
var colors = Logger.colors;
var format = util.format;
var Utils = require('../util/utils.js');
var Q = require('q');

var Reporter = module.exports = function(globalResults, testResults, globalStartTime, options) {
  this.globalResults = globalResults;
  this.testResults = testResults;
  this.globalStartTime = globalStartTime;
  this.options = options;
  this.reporter = options.reporter || 'junit';
};

var printf = function() {
  console.log(format.apply(null, arguments));
};

/**
 * @static
 * @param err
 * @param testname
 * @param time
 * @return {string}
 */
Reporter.getTestOutput = function (err, testname, time) {
  var symbol;
  if (Utils.isErrorObject(err)) {
    symbol = Logger.colors.red(Utils.symbols.fail);
    testname = Logger.colors.red(testname);
  } else {
    symbol = Logger.colors.green(Utils.symbols.ok);
  }

  var args = [
    '%s %s', symbol, testname
  ];

  if (time > 20) {
    args[0] += ' %s';
    args.push(Logger.colors.yellow(Utils.format('(%s)', Utils.formatElapsedTime(time))));
  }

  return Utils.format.apply(null, args);
};

Reporter.printAssertions = function(test) {
  test.assertions.forEach(function(a) {
    if (a.failure !== false) {
      var message = a.stackTrace.split('\n');
      message.unshift(a.fullMsg);
      Utils.showStackTrace(message.join('\n'));
    }
  });

  if (test.stackTrace) {
    Utils.showStackTrace(test.stackTrace);
  }
};

Reporter.prototype.get = function() {
  var fileName = __dirname + '/reporters/' + this.reporter + '.js';

  if (fs.existsSync(fileName)) {
    return require(fileName);
  }

  fileName = path.resolve(this.reporter);

  if (fs.existsSync(fileName)) {
    var reporter = require(fileName);
    if (typeof reporter.write == 'function') {
      return reporter;
    }
    throw new Error('The reporter module must have a public `write` method defined.');
  }

  throw new Error('The reporter file name cannot be resolved. Using path: ' + fileName);
};

/**
 * @param {object} globals
 * @returns {function}
 */
Reporter.prototype.globalReporter = function(globals) {
  var reporterFn = Utils.checkFunction('reporter', globals) || function() {};
  return Utils.makeFnAsync(2, reporterFn, globals);
};

Reporter.prototype.isDisabled = function() {
  return this.options.output_folder === false;
};

Reporter.prototype.createFolder = function(cb) {
  if (this.isDisabled()) {
    cb(null);
  } else {
    mkpath(this.options.output_folder, cb);
  }
};

Reporter.prototype.save = function() {
  var self = this;
  var deferred = Q.defer();
  this.createFolder(function(err) {
    if (self.isDisabled()) {
      deferred.resolve();
    } else {
      if (err) {
        deferred.reject(err);
        return;
      }

      var reporter = self.get();

      reporter.write(self.globalResults, self.options, function(err) {
        if (err) {
          console.log(colors.yellow(format('Warning: Failed to save report file to folder: %s', self.options.output_folder)));
          console.log(err.stack);
        }
        deferred.resolve(err);
      });
    }
  });

  return deferred.promise;
};

Reporter.prototype.printTotalResults = function() {
  var elapsedTime = new Date().getTime() - this.globalStartTime;
  process.stdout.write('\n');

  if (this.testsFailed()) {
    var countMessage = this.getTestsFailedMessage();

    console.log(colors.light_red(' _________________________________________________'));
    console.log(
      format('\n %s', colors.light_red('TEST FAILURE:')),
      countMessage,
      format('(%s)', Utils.formatElapsedTime(elapsedTime))
    );


    this.printFailureSummary();
    console.log('');
  } else {
    if (!this.shouldShowSummary()) {
      return;
    }

    var message = this.getTestsPassedMessage();
    printf('%s (%s)', message, Utils.formatElapsedTime(elapsedTime));
  }
};

Reporter.prototype.testsFailed = function() {
  return Object.keys(this.globalResults.modules).some(function(moduleKey) {
    return this.globalResults.modules[moduleKey].failures > 0 || this.globalResults.modules[moduleKey].errors > 0;
  }.bind(this));
};

Reporter.prototype.shouldShowSummary = function() {
  var modules = Object.keys(this.globalResults.modules);
  if (modules.length > 1) {
    return true;
  }

  if (modules.length <= 0) {
    return false;
  }

  return  Object.keys(this.globalResults.modules[modules[0]].completed).length > 1;
};

Reporter.prototype.hasAssertionCount = function() {
  return Object.keys(this.globalResults.modules).length > 0 &&
    (this.globalResults.failed > 0 || this.globalResults.passed > 0);
};

Reporter.prototype.getTestsPassedMessage = function() {
  var hasCount = this.hasAssertionCount();
  var message;
  var count;

  if (hasCount) {
    count = this.globalResults.passed;
    message = colors.green(format('OK. %s %s passed.', count, (count > 1 ? ' total assertions' : ' assertion')));
  } else {
    count = this.getTotalTestsCount();
    message = format('%s tests passed.', colors.green('OK. ' + count));
  }

  return message;
};

Reporter.prototype.getTotalTestsCount = function() {

  var module;

  return Object.keys(this.globalResults.modules).reduce(function(count, moduleKey) {
    module = this.globalResults.modules[moduleKey];
    return count + module.tests - module.skipped.length;
  }.bind(this), 0);
};

Reporter.prototype.getTestsFailedMessage = function() {
  var hasCount = this.hasAssertionCount();
  if (!hasCount && this.testResults.errmessages === 0) {
    return '';
  }
  var errorsMsg = '';
  var failedMsg = 'assertions';
  var passedMsg = format('%s passed', colors.green(this.globalResults.passed));

  if (!this.options.start_session) {
    failedMsg = 'tests';
    var passedCount = Math.max(0, this.getTotalTestsCount() - this.globalResults.failed);
    passedMsg = format('%s passed', colors.green(passedCount));
  }

  var skipped = '';
  if (this.testResults.skipped) {
    skipped = format(' and %s skipped', colors.cyan(this.testResults.skipped));
  }

  if (this.globalResults.errors) {
    var suffix = this.globalResults.errors > 1 ? 's' : '';
    errorsMsg += format('%s error%s during execution, ', colors.red(this.globalResults.errors), suffix);
  }

  return format('%s %s %s failed, %s%s.', errorsMsg, colors.red(this.globalResults.failed), failedMsg, passedMsg, skipped);
};

Reporter.prototype.printFailureSummary = function() {
  Object.keys(this.globalResults.modules).forEach(function(moduleKey) {
    var testSuite = this.globalResults.modules[moduleKey];
    if (testSuite.failures > 0 || testSuite.errors > 0) {

      console.log('\n' + colors.red(format(' %s %s', Utils.symbols.fail, moduleKey)));

      Object.keys(testSuite.completed).forEach(function(testcase) {
        var test = testSuite.completed[testcase];
        if (test.failed > 0 || test.errors > 0) {
          printf('\n   - %s %s', testcase, colors.yellow('(' + Utils.formatElapsedTime(test.timeMs) + ')'));

          if (test.assertions.length > 0 && this.options.start_session) {
            Reporter.printAssertions(test);
          } else if (test.stackTrace) {
            Utils.showStackTrace(test.stackTrace);
          }
        }
      }.bind(this));

      if (Array.isArray(testSuite.errmessages)) {
        testSuite.errmessages.forEach(function(err) {
          console.log('');
          Utils.showStackTrace(err);
          console.log('');
        });
      }


      if (testSuite.skipped.length > 0) {
        console.log(colors.cyan('   SKIPPED:'));
        testSuite.skipped.forEach(function(testcase) {
          printf('   - %s', testcase);
        });
      }
    }
  }.bind(this));

  if (Array.isArray(this.globalResults.errmessages)) {
    this.globalResults.errmessages.forEach(function(err) {
      console.log('');
      Utils.showStackTrace(err);
      console.log('');
    }.bind(this));
  }
};
