package parse

import (
	"net/http"
	"net/url"
	"strings"

	"github.com/rancher/steve/pkg/schemaserver/types"
	"github.com/rancher/steve/pkg/schemaserver/urlbuilder"
)

const (
	maxFormSize = 2 * 1 << 20
)

var (
	allowedFormats = map[string]bool{
		"html": true,
		"json": true,
		"yaml": true,
	}
)

type ParsedURL struct {
	Type       string
	Name       string
	Namespace  string
	Link       string
	Method     string
	Action     string
	Prefix     string
	SubContext map[string]string
	Query      url.Values
}

type URLParser func(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error)

type Parser func(apiOp *types.APIRequest, urlParser URLParser) error

func Parse(apiOp *types.APIRequest, urlParser URLParser) error {
	var err error

	if apiOp.Request == nil {
		apiOp.Request, err = http.NewRequest("GET", "/", nil)
		if err != nil {
			return err
		}
	}

	apiOp = types.StoreAPIContext(apiOp)

	if apiOp.Method == "" {
		apiOp.Method = parseMethod(apiOp.Request)
	}
	if apiOp.ResponseFormat == "" {
		apiOp.ResponseFormat = parseResponseFormat(apiOp.Request)
	}

	// The response format is guaranteed to be set even in the event of an error
	parsedURL, err := urlParser(apiOp.Response, apiOp.Request, apiOp.Schemas)
	// wait to check error, want to set as much as possible

	if apiOp.Type == "" {
		apiOp.Type = parsedURL.Type
	}
	if apiOp.Name == "" {
		apiOp.Name = parsedURL.Name
	}
	if apiOp.Link == "" {
		apiOp.Link = parsedURL.Link
	}
	if apiOp.Action == "" {
		apiOp.Action = parsedURL.Action
	}
	if apiOp.Query == nil {
		apiOp.Query = parsedURL.Query
	}
	if apiOp.Method == "" && parsedURL.Method != "" {
		apiOp.Method = parsedURL.Method
	}
	if apiOp.URLPrefix == "" {
		apiOp.URLPrefix = parsedURL.Prefix
	}
	if apiOp.Namespace == "" {
		apiOp.Namespace = parsedURL.Namespace
	}

	if apiOp.URLBuilder == nil {
		// make error local to not override the outer error we have yet to check
		var err error
		apiOp.URLBuilder, err = urlbuilder.New(apiOp.Request, &urlbuilder.DefaultPathResolver{
			Prefix: apiOp.URLPrefix,
		}, apiOp.Schemas)
		if err != nil {
			return err
		}
	}

	if err != nil {
		return err
	}

	if apiOp.Schema == nil && apiOp.Schemas != nil {
		apiOp.Schema = apiOp.Schemas.LookupSchema(apiOp.Type)
	}

	if apiOp.Schema != nil && apiOp.Type == "" {
		apiOp.Type = apiOp.Schema.ID
	}

	if err := ValidateMethod(apiOp); err != nil {
		return err
	}

	return nil
}

func parseResponseFormat(req *http.Request) string {
	format := req.URL.Query().Get("_format")

	if format != "" {
		format = strings.TrimSpace(strings.ToLower(format))
	}

	/* Format specified */
	if allowedFormats[format] {
		return format
	}

	// User agent has Mozilla and browser accepts */*
	if IsBrowser(req, true) {
		return "html"
	}

	if isYaml(req) {
		return "yaml"
	}
	return "json"
}

func isYaml(req *http.Request) bool {
	return strings.Contains(req.Header.Get("Accept"), "application/yaml")
}

func parseMethod(req *http.Request) string {
	method := req.URL.Query().Get("_method")
	if method == "" {
		method = req.Method
	}
	return method
}

func Body(req *http.Request) (types.APIObject, error) {
	req.ParseMultipartForm(maxFormSize)
	if req.MultipartForm != nil {
		return valuesToBody(req.MultipartForm.Value), nil
	}

	if req.PostForm != nil && len(req.PostForm) > 0 {
		return valuesToBody(map[string][]string(req.Form)), nil
	}

	return ReadBody(req)
}

func valuesToBody(input map[string][]string) types.APIObject {
	result := map[string]interface{}{}
	for k, v := range input {
		result[k] = v
	}
	return toAPI(result)
}
