package api

import (
	"encoding/json"
	"net/http"

	validator "gopkg.in/go-playground/validator.v9"
)

const (
	ErrCodeMalformedRequest  ErrorCode = "malformed-request"
	ErrCodeInvalidRequest    ErrorCode = "invalid-request"
	ErrCodeInvalidFieldValue ErrorCode = "invalid-field-value"
)

// Bind parses and bind the request body to the given target
// Bind wil also validates the received data with a validator instance.
// If the data does not match the target constraints, an ErrorResponse will be
// sent back.
// See gopkg.in/go-playground/validator.v9 for more informations about data
// validation.
func Bind(w http.ResponseWriter, r *http.Request, target interface{}) (ok bool) {
	if err := parseBody(r, target); err != nil {
		ErrorResponse(
			w, http.StatusBadRequest,
			ErrCodeMalformedRequest,
			nil,
		)
		return false
	}
	validate := validator.New()
	if err := validate.Struct(target); err != nil {
		handleValidateError(w, err)
		return false
	}
	return true
}

func parseBody(r *http.Request, target interface{}) error {
	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(target); err != nil {
		return err
	}
	return nil
}

type fieldError struct {
	Field string
	Tag   string
	Param string
}

func handleValidateError(w http.ResponseWriter, err error) {
	if validationErrors, ok := err.(validator.ValidationErrors); ok {
		fields := make([]fieldError, 0)
		for _, e := range validationErrors {
			fields = append(fields, fieldError{
				Field: e.Field(),
				Tag:   e.Tag(),
				Param: e.Param(),
			})
		}
		ErrorResponse(
			w, http.StatusBadRequest,
			ErrCodeInvalidFieldValue,
			map[string]interface{}{
				"Fields": fields,
			},
		)
		return
	}
	ErrorResponse(
		w, http.StatusBadRequest,
		ErrCodeInvalidRequest,
		nil,
	)
}