// Copyright (c) 2023 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package handler

import (
	"net/url"
	"strings"

	cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
)

// extracToken 从三个来源中依次尝试抽取Token，若找不到Token则返回空字符串
func extractToken(keepToken bool, consumer *cfg.Consumer, header HeaderProvider, log Logger) string {
	token := ""

	// 1. 从header中抽取token
	if h := consumer.FromHeaders; h != nil {
		token = extractFromHeader(keepToken, *h, header, log)
	}
	if token != "" {
		return token
	}

	// 2. 从params中抽取token
	if p := consumer.FromParams; p != nil {
		token = extractFromParams(keepToken, *p, header, log)
	}
	if token != "" {
		return token
	}

	// 3. 从cookies中抽取token
	if c := consumer.FromCookies; c != nil {
		token = extractFromCookies(keepToken, *c, header, log)
	}

	// 此处无需判空
	return token
}

func extractFromHeader(keepToken bool, headers []cfg.FromHeader, header HeaderProvider, log Logger) (token string) {
	for i := range headers {

		// proxywasm 获取到的 header name 均为小写，此处需做修改
		lowerName := strings.ToLower(headers[i].Name)
		token, err := header.GetHttpRequestHeader(lowerName)
		if err != nil {
			log.Warnf("failed to get authorization: %v", err)
			continue
		}

		if token != "" {
			if !strings.HasPrefix(token, headers[i].ValuePrefix) {
				log.Warnf("authorization has no prefix %q", headers[i].ValuePrefix)
				return ""
			}
			if !keepToken {
				_ = header.RemoveHttpRequestHeader(lowerName)
			}
			return strings.TrimPrefix(token, headers[i].ValuePrefix)
		}
	}
	return ""
}

func extractFromParams(keepToken bool, params []string, header HeaderProvider, log Logger) (token string) {
	urlparams, err := header.GetHttpRequestHeader(":path")
	if err != nil {
		log.Warnf("failed to get authorization: %v", err)
		return ""
	}

	url, _ := url.Parse(urlparams)
	query := url.Query()

	for i := range params {
		token := query.Get(params[i])
		if token != "" {
			if !keepToken {
				query.Del(params[i])
			}
			return token
		}
	}
	return ""
}

func extractFromCookies(keepToken bool, cookies []string, header HeaderProvider, log Logger) (token string) {
	requestCookies, err := header.GetHttpRequestHeader("cookie")
	if err != nil {
		log.Warnf("failed to get authorization: %v", err)
		return ""
	}

	for i := range cookies {
		token := findCookie(requestCookies, cookies[i])
		if token != "" {
			if !keepToken {
				_ = header.ReplaceHttpRequestHeader("cookie", deleteCookie(requestCookies, cookies[i]))
			}
			return token
		}
	}

	return ""
}

func findCookie(cookie string, key string) string {
	value := ""
	pairs := strings.Split(cookie, ";")

	for _, pair := range pairs {
		pair = strings.TrimSpace(pair)
		kv := strings.Split(pair, "=")
		if kv[0] == key {
			value = kv[1]
			break
		}
	}
	return value
}

func deleteCookie(cookie string, key string) string {
	result := ""
	pairs := strings.Split(cookie, ";")

	for _, pair := range pairs {
		pair = strings.TrimSpace(pair)
		if !strings.HasPrefix(pair, key) {
			result += pair + ";"
		}
	}
	return strings.TrimSuffix(result, ";")
}
