package api

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"strings"

	"bitbucket.org/ww/goautoneg"

	"code.secondbit.org/uuid.hg"

	"golang.org/x/net/context"
)

const (
	RequestErrAccessDenied  = "access_denied"
	RequestErrInsufficient  = "insufficient"
	RequestErrOverflow      = "overflow"
	RequestErrInvalidValue  = "invalid_value"
	RequestErrInvalidFormat = "invalid_format"
	RequestErrMissing       = "missing"
	RequestErrNotFound      = "not_found"
	RequestErrConflict      = "conflict"
	RequestErrActOfGod      = "act_of_god"
)

var (
	ActOfGodError      = []RequestError{{Slug: RequestErrActOfGod}}
	InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}}

	Encoders = []string{"application/json"}

	ErrUserIDNotSet = errors.New("user ID not set")
)

type RequestError struct {
	Slug   string `json:"error,omitempty"`
	Field  string `json:"field,omitempty"`
	Param  string `json:"param,omitempty"`
	Header string `json:"header,omitempty"`
}

type ContextHandler func(http.ResponseWriter, *http.Request, context.Context)

func NegotiateMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Accept") != "" {
			contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
			if contentType == "" {
				w.WriteHeader(http.StatusNotAcceptable)
				w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept")))
				return
			}
		}
		h.ServeHTTP(w, r)
	})
}

func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) {
	contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
	w.Header().Set("content-type", contentType)
	w.WriteHeader(status)
	var err error
	switch contentType {
	case "application/json":
		enc := json.NewEncoder(w)
		err = enc.Encode(resp)
	default:
		enc := json.NewEncoder(w)
		err = enc.Encode(resp)
	}
	if err != nil {
		log.Println(err)
	}
}

func Decode(r *http.Request, target interface{}) error {
	defer r.Body.Close()
	switch r.Header.Get("Content-Type") {
	case "application/json":
		dec := json.NewDecoder(r.Body)
		return dec.Decode(target)
	default:
		dec := json.NewDecoder(r.Body)
		return dec.Decode(target)
	}
}

func CORSMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		if strings.ToLower(r.Method) == "options" {
			methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ")
			w.Header().Set("Access-Control-Allow-Methods", methods)
			w.Header().Set("Allow", methods)
			w.WriteHeader(http.StatusOK)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func ContextWrapper(c context.Context, handler ContextHandler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		handler(w, r, c)
	})
}

func CheckScopes(r *http.Request, scopes ...string) bool {
	passedStr := r.Header.Get("scopes")
	passed := strings.Split(passedStr, " ")
	for _, scope := range scopes {
		var found bool
		for _, p := range passed {
			if scope == strings.TrimSpace(p) {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

func AuthUser(r *http.Request) (uuid.ID, error) {
	rawID := r.Header.Get("User-ID")
	if rawID == "" {
		return nil, ErrUserIDNotSet
	}
	return uuid.Parse(rawID)
}
