package gateway

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/TykTechnologies/gojsonschema"
	"github.com/TykTechnologies/tyk/apidef"
)

type ValidateJSON struct {
	BaseMiddleware
}

func (k *ValidateJSON) Name() string {
	return "ValidateJSON"
}

func (k *ValidateJSON) EnabledForSpec() bool {
	for _, v := range k.Spec.VersionData.Versions {
		if len(v.ExtendedPaths.ValidateJSON) > 0 {
			return true
		}
	}

	return false
}

// ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
func (k *ValidateJSON) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {

	_, versionPaths, _, _ := k.Spec.Version(r)
	found, meta := k.Spec.CheckSpecMatchesStatus(r, versionPaths, ValidateJSONRequest)
	if !found {
		return nil, http.StatusOK
	}

	vPathMeta := meta.(*apidef.ValidatePathMeta)
	if vPathMeta.Schema == nil {
		return errors.New("no schemas to validate against"), http.StatusInternalServerError
	}

	if val, exists := vPathMeta.Schema["$schema"]; exists {
		if val != "http://json-schema.org/draft-04/schema#" {
			return errors.New("unsupported schema, unable to validate"), http.StatusInternalServerError
		}
	}

	// Load input body into gojsonschema
	bodyBytes, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err, http.StatusBadRequest
	}
	defer r.Body.Close()
	inputLoader := gojsonschema.NewBytesLoader(bodyBytes)

	// Perform validation
	result, err := gojsonschema.Validate(vPathMeta.SchemaCache, inputLoader)
	if err != nil {
		return fmt.Errorf("JSON parsing error: %v", err), http.StatusBadRequest
	}

	// Handle Failure
	if !result.Valid() {
		if vPathMeta.ErrorResponseCode == 0 {
			vPathMeta.ErrorResponseCode = http.StatusUnprocessableEntity
		}

		return k.formatError(result.Errors()), vPathMeta.ErrorResponseCode
	}

	// Handle Success
	return nil, http.StatusOK
}

func (k *ValidateJSON) formatError(schemaErrors []gojsonschema.ResultError) error {
	errStr := ""
	for i, desc := range schemaErrors {
		if i == 0 {
			errStr = desc.String()
		} else {
			errStr = errStr + "; " + desc.String()
		}
	}

	return errors.New(errStr)
}
