"use strict";

var samsam = require("@sinonjs/samsam");
var hasOwnProperty = require("@sinonjs/commons").prototypes.object
  .hasOwnProperty;

var captureException = require("../capture-exception");

var typeNoExceptionMessage =
  "${customMessage}Expected ${expected} but no exception was thrown";
var typeFailMessage =
  "${customMessage}Expected ${expected} but threw ${actualExceptionType} (${actualExceptionMessage})\n${actualExceptionStack}";
var matchFailMessage =
  "${customMessage}Expected thrown ${actualExceptionType} (${actualExceptionMessage}) to pass matcher function";

module.exports = function (referee) {
  referee.captureException = captureException;

  referee.add("exception", {
    assert: function (callback) {
      var matcher = arguments[1];
      var customMessage = arguments[2];

      if (typeof matcher === "string") {
        customMessage = matcher;
        matcher = undefined;
      }

      this.expected = matcher;
      this.customMessage = customMessage;

      var err = captureException(callback);

      if (err) {
        this.actualExceptionType = err.name;
        this.actualExceptionMessage = err.message;
        this.actualExceptionStack = err.stack;
      }

      if (!err) {
        if (typeof matcher === "object") {
          return this.fail(typeNoExceptionMessage);
        }
        return this.fail("message");
      }

      if (typeof matcher === "object" && !samsam.match(err, matcher)) {
        var matches = false;
        var prop;
        for (prop in matcher) {
          if (hasOwnProperty(matcher, prop)) {
            if (!samsam.match(err[prop], matcher[prop])) {
              return this.fail(typeFailMessage);
            }
            matches = true;
          }
        }
        return matches;
      }

      if (typeof matcher === "function" && matcher(err) !== true) {
        return this.fail(matchFailMessage);
      }

      return true;
    },

    refute: function (callback) {
      var err = captureException(callback);

      if (err) {
        this.customMessage = arguments[1];
        this.actualExceptionType = err.name;
        this.actualExceptionMessage = err.message;
        return false;
      }

      return true;
    },

    expectation: "toThrow",
    assertMessage: "${customMessage}Expected exception",
    refuteMessage:
      "${customMessage}Expected not to throw but threw ${actualExceptionType} (${actualExceptionMessage})",
  });
};
