package session

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

	"github.com/Kong/go-pdk"
	"github.com/pingcap/errors"
	"golang.org/x/net/http/httpguts"
)

func getCookies(kong *pdk.PDK) []string {
	headers, err := kong.Request.GetHeaders(-1)
	if err != nil {
		_ = kong.Log.Err("get header failed", err.Error())
		return []string{}
	}

	if len(headers) == 0 {
		return []string{}
	}

	cookies, ok := headers["Cookie"]
	if ok {
		return cookies
	}

	cookie, err := kong.Request.GetHeader("Cookie")
	if err != nil {
		_ = kong.Log.Err("get header failed", err.Error())
		return []string{}
	}

	if len(cookie) == 0 {
		return []string{}
	}

	return []string{cookie}
}

func Cookie(kong *pdk.PDK, name string) (string, error) {
	cookie, err := cookie(kong, name)
	if err != nil {
		return "", err
	}
	val, _ := url.QueryUnescape(cookie.Value)
	return val, nil
}

func cookie(kong *pdk.PDK, name string) (*http.Cookie, error) {
	for _, c := range readCookies(kong, name) {
		return c, nil
	}
	return nil, errors.New("http: named cookie not present")
}

func readCookies(kong *pdk.PDK, filter string) []*http.Cookie {
	lines := getCookies(kong)
	if len(lines) == 0 {
		return []*http.Cookie{}
	}

	cookies := make([]*http.Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
	for _, line := range lines {
		line = textproto.TrimString(line)

		var part string
		for len(line) > 0 { // continue since we have rest
			if splitIndex := strings.Index(line, ";"); splitIndex > 0 {
				part, line = line[:splitIndex], line[splitIndex+1:]
			} else {
				part, line = line, ""
			}
			part = textproto.TrimString(part)
			if len(part) == 0 {
				continue
			}
			name, val := part, ""
			if j := strings.Index(part, "="); j >= 0 {
				name, val = name[:j], name[j+1:]
			}
			if !isCookieNameValid(name) {
				continue
			}
			if filter != "" && filter != name {
				continue
			}
			val, ok := parseCookieValue(val, true)
			if !ok {
				continue
			}
			cookies = append(cookies, &http.Cookie{Name: name, Value: val})
		}
	}
	return cookies
}

func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
	// Strip the quotes, if present.
	if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
		raw = raw[1 : len(raw)-1]
	}
	for i := 0; i < len(raw); i++ {
		if !validCookieValueByte(raw[i]) {
			return "", false
		}
	}
	return raw, true
}

func isCookieNameValid(raw string) bool {
	if raw == "" {
		return false
	}
	return strings.IndexFunc(raw, isNotToken) < 0
}

func validCookieValueByte(b byte) bool {
	return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
}

func isNotToken(r rune) bool {
	return !httpguts.IsTokenRune(r)
}
