'use strict';

/**
  * Stubby JSON Schema Validator
  * Depends on tv4, lodash, and RouteRecognizer
  */

var stubbySchemaValidatorModule = function(deps) {
  return function() {

    var _ = deps.lodash;
    this.validator = deps.tv4.freshApi();
    this.schemaCount = 0;
    this.hyperschemaUrls = {};
    var RouteRecognizer = deps.routerecognizer;
    this.router = new RouteRecognizer();

    this.addSchema = function(uri, schema) {
      this.validator.addSchema(uri, schema);
      this.processNewHyperschema(schema);
      this.schemaCount += 1;
    };

    this.register = function(handler) {
      handler.on('request', this.onRequestExecute, this);
      handler.on('routesetup', this.onRequestExecute, this);
    };

    this.onRequestExecute = function(request, stub) {
      if (stub.internal.options.validateSchema === false) {
        return null;
      }
      return this.validate(request, stub);
    };

    this.parseQueryParamsSchema = function(params) {
      var paramsParsed = {};
      Object.keys(params).forEach(function(paramName) {
        var paramMatch = paramName.match(/^(.+)\[(.+)\]$/);
        var paramValue = params[paramName];
        if (paramMatch) {
          var matchPath = paramsParsed[paramMatch[1]];
          if (!matchPath) {
            matchPath = {};
          }
          matchPath[paramMatch[2]] = paramValue;
        } else {
          paramsParsed[paramName] = paramValue;
        }
      });
      return paramsParsed;
    };

    this.processNewHyperschema = function(rawSchema) {
      var self = this;
      Object.keys(rawSchema.definitions).forEach(function(descKey) {
        var val = rawSchema.definitions[descKey];
        val.links.forEach(function(linkSchema) {
          var urlToAdd = linkSchema.href;

          if (!self.hyperschemaUrls[urlToAdd]) {
            self.hyperschemaUrls[urlToAdd] = [];
          }

          self.hyperschemaUrls[urlToAdd].push(linkSchema);
          if (urlToAdd.match(/\{(.+)\}/)) {
            var paramsToMatch = decodeURIComponent(urlToAdd);
            var doReplaceParam = function(match, param) {
              linkSchema.baseUrlToAdd = param;
              return ':id';
            };
            urlToAdd = paramsToMatch.replace(/\{\(([^)]+)\)\}/, doReplaceParam);
          }
          self.router.add([{path: urlToAdd, handler: linkSchema.href}]);
        });
      });
    };

    this.getSchemaForRoute = function(stub, routeRef) {
      return _.find(this.hyperschemaUrls[routeRef], function(schema) {
        if (schema.method === stub.request.method) {
          return true;
        }
      });
    };

    this.validateRequestSchema = function(stub, request, schema) {
      var keyTraverse = _.find(Object.keys(request.data), function(key) {
        return (stub.url.indexOf(key) !== -1);
      });
      var requestData = request.data;
      if (keyTraverse) {
        requestData = requestData[keyTraverse];
      }

      var queryParams = this.parseQueryParamsSchema(stub.queryParams);
      var req = _.extend({}, queryParams, requestData);

      // An empty request is valid. (in the case of gocardless' schema)
      if (_.isEmpty(req)) { return; }

      var valResponse = this.validator.validateMultiple(req, schema, true, false);
      if (valResponse.errors.length > 0 || valResponse.missing.length > 0) {
        var stubInfo = stub.request.method + ' ' + stub.url + ' (' + JSON.stringify(req) + ')';
        throw new Error('Validation Failed for request: ' + stubInfo + ' ' + JSON.stringify(valResponse));
      }
    };

    this.validateEmptyQueryParams = function(stub) {
      if (!_.isEmpty(stub.request.data) && !_.isEmpty(stub.queryParams)) {
        throw new Error('Parameters provided to a parameterless route (' +
                        JSON.stringify([stub.request, stub.queryParams, stub.url]) + ')');
      }
    };

    this.validate = function(request, stub) {
      request.data = {};
      if (request.requestBody) {
        request.data = JSON.parse(request.requestBody) || {};
      }

      var routes = this.router.recognize(stub.url);
      if (!routes) {
        throw new Error('URL (' + stub.url + ') is undefined for the API');
      }
      var routeSchema = this.getSchemaForRoute(stub, routes[0].handler);
      if (routeSchema) {
        this.validateRequestSchema(stub, request, routeSchema.schema);
      } else {
        this.validateEmptyQueryParams(stub);
      }
    };
  };
};


if (typeof module === 'undefined') {
  var dependencies = {
    lodash: window._,
    routerecognizer: window.RouteRecognizer,
    tv4: window.tv4
  };
  Object.keys(dependencies).forEach(function(dependency) {
    if (typeof dependencies[dependency] === 'undefined') {
      throw new Error(['[stubby schema-validator] Missing ', dependency, ' library.'].join(' '));
    }
  });
  window.StubbySchemaValidator = stubbySchemaValidatorModule(dependencies);
} else {
  module.exports = stubbySchemaValidatorModule({
    lodash: require('lodash'),
    routerecognizer: require('route-recognizer'),
    tv4: require('tv4')
  });
}


@@ -3009,64 +3009,8 @@
 ms);
-%0A      var req = _.extend(%7B%7D, queryParams, requestData);
 %0A%0A  
@@ -3103,27 +3103,114 @@
 pty(
-req)) %7B return; %7D%0A%0A
+queryParams) && _.isEmpty(requestData)) %7B return; %7D%0A%0A      function validateReq(validator, req, type) %7B%0A  
     
@@ -3229,21 +3229,16 @@
 ponse = 
-this.
 validato
@@ -3283,16 +3283,18 @@
 false);%0A
+  
       if
@@ -3366,24 +3366,26 @@
 ) %7B%0A        
+  
 var stubInfo
@@ -3460,32 +3460,34 @@
  + ')';%0A        
+  
 throw new Error(
@@ -3487,17 +3487,24 @@
  Error('
-V
+Schema v
 alidatio
@@ -3505,17 +3505,17 @@
 idation 
-F
+f
 ailed fo
@@ -3516,23 +3516,28 @@
 led for 
-request
+' + type + '
 : ' + st
@@ -3578,32 +3578,237 @@
 esponse));%0A     
+   %7D%0A      %7D%0A%0A      if (!_.isEmpty(queryParams)) %7B validateReq(this.validator, queryParams, 'query params'); %7D%0A      if (!_.isEmpty(requestData)) %7B validateReq(this.validator, requestData, 'request data');
  %7D%0A    %7D;%0A%0A    t

Stop munging request data into an object when it's not