package http

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/pkg/capnslog"
)

var (
	log = capnslog.NewPackageLogger("github.com/coreos/go-oidc", "http")
)

func WriteError(w http.ResponseWriter, code int, msg string) {
	e := struct {
		Error string `json:"error"`
	}{
		Error: msg,
	}
	b, err := json.Marshal(e)
	if err != nil {
		log.Errorf("Failed marshaling %#v to JSON: %v", e, err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	w.Write(b)
}

// BasicAuth parses a username and password from the request's
// Authorization header. This was pulled from golang master:
// https://codereview.appspot.com/76540043
func BasicAuth(r *http.Request) (username, password string, ok bool) {
	auth := r.Header.Get("Authorization")
	if auth == "" {
		return
	}

	if !strings.HasPrefix(auth, "Basic ") {
		return
	}
	c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
	if err != nil {
		return
	}
	cs := string(c)
	s := strings.IndexByte(cs, ':')
	if s < 0 {
		return
	}
	return cs[:s], cs[s+1:], true
}

func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
	for _, field := range strings.Split(hdr, ",") {
		parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
		k := strings.ToLower(strings.TrimSpace(parts[0]))
		if k != "max-age" {
			continue
		}

		if len(parts) == 1 {
			return 0, false, errors.New("max-age has no value")
		}

		v := strings.TrimSpace(parts[1])
		if v == "" {
			return 0, false, errors.New("max-age has empty value")
		}

		age, err := strconv.Atoi(v)
		if err != nil {
			return 0, false, err
		}

		if age <= 0 {
			return 0, false, nil
		}

		return time.Duration(age) * time.Second, true, nil
	}

	return 0, false, nil
}

func expires(date, expires string) (time.Duration, bool, error) {
	if date == "" || expires == "" {
		return 0, false, nil
	}

	te, err := time.Parse(time.RFC1123, expires)
	if err != nil {
		return 0, false, err
	}

	td, err := time.Parse(time.RFC1123, date)
	if err != nil {
		return 0, false, err
	}

	ttl := te.Sub(td)

	// headers indicate data already expired, caller should not
	// have to care about this case
	if ttl <= 0 {
		return 0, false, nil
	}

	return ttl, true, nil
}

func Cacheable(hdr http.Header) (time.Duration, bool, error) {
	ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
	if err != nil || ok {
		return ttl, ok, err
	}

	return expires(hdr.Get("Date"), hdr.Get("Expires"))
}

// MergeQuery appends additional query values to an existing URL.
func MergeQuery(u url.URL, q url.Values) url.URL {
	uv := u.Query()
	for k, vs := range q {
		for _, v := range vs {
			uv.Add(k, v)
		}
	}
	u.RawQuery = uv.Encode()
	return u
}

// NewResourceLocation appends a resource id to the end of the requested URL path.
func NewResourceLocation(reqURL *url.URL, id string) string {
	var u url.URL
	u = *reqURL
	u.Path = path.Join(u.Path, id)
	u.RawQuery = ""
	u.Fragment = ""
	return u.String()
}

// CopyRequest returns a clone of the provided *http.Request.
// The returned object is a shallow copy of the struct and a
// deep copy of its Header field.
func CopyRequest(r *http.Request) *http.Request {
	r2 := *r
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return &r2
}
