package auth

import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type whitelistRequestPlugin struct {
	Whitelist []ConfigPathVal
}

func NewWhitelistRequestPlugin(whitelist ...string) (RequestPlugin, error) {
	formatWhitelistMap, err := validateAndFormatWhitelist(whitelist...)
	if err != nil {
		return nil, err
	}
	return &whitelistRequestPlugin{
		Whitelist: formatWhitelistMap,
	}, nil
}

func NewWhitelistRequestFunc(whitelist ...string) (ReleaseRequestFunc, error) {
	helper, err := NewWhitelistRequestPlugin(whitelist...)
	if err != nil {
		return nil, err
	}
	return func(ctx context.Context, param *AccessParam) (*RequestRet, error) {
		return helper.ReleaseRequest(ctx, param)
	}, nil
}

func (d *whitelistRequestPlugin) ReleaseRequest(_ context.Context, param *AccessParam) (*RequestRet, error) {
	if d.Whitelist == nil || len(d.Whitelist) == 0 {
		return &RequestRet{
			Released: false,
			Message:  "white list is empty",
		}, nil
	}
	path := getRequestPath(param)
	method := getRequestMethod(param)
	for _, wl := range d.Whitelist {
		if !regexp.MustCompile(wl.ExactMatchPath).MatchString(path) {
			continue
		}
		if len(wl.Methods) == 0 {
			return &RequestRet{
				Released: true,
				Message:  fmt.Sprintf("request %s:%s hit white list %s", method, path, wl.ExactMatchPath),
			}, nil
		}
		rm := strings.ToLower(method)
		for _, m := range wl.Methods {
			if m == rm {
				return &RequestRet{
					Released: true,
					Message:  fmt.Sprintf("request %s:%s hit white list %s:%s", method, path, rm, wl.ExactMatchPath),
				}, nil
			}
		}
	}
	return &RequestRet{
		Released: false,
		Message:  "not hit white list",
	}, nil
}

func validateAndFormatWhitelist(whitelist ...string) ([]ConfigPathVal, error) {
	var configWhitelist []ConfigPathVal
	if whitelist != nil && len(whitelist) > 0 {
		for _, wl := range whitelist {
			if wl == "" {
				return nil, errors.New("whitelist path is blank")
			}
			path, methods, err := parsePathPattern(wl)
			if err != nil {
				return nil, err
			}
			exactMatchPath := fmt.Sprintf(ExactMatchFormatter, path)
			_, err = regexp.Compile(exactMatchPath)
			if err != nil {
				return nil, errors.New(fmt.Sprintf("whitelist path invalid: %s, err: %s", wl, err.Error()))
			}
			val := ConfigPathVal{
				ExactMatchPath: exactMatchPath,
				Methods:        methods,
			}
			configWhitelist = append(configWhitelist, val)
		}
	}
	return configWhitelist, nil
}

func parsePathPattern(pathPattern string) (path string, method []string, err error) {
	// get,post:/api/v1/orgs/create

	var pathStr, methodStr = "", ""

	pathPattern = strings.TrimSpace(pathPattern)
	hasPre := strings.HasPrefix(pathPattern, PathPrefix)
	if hasPre {
		pathStr = pathPattern
	} else {
		index := strings.Index(pathPattern, PathMethodSeparator)
		if index < 0 {
			return "", nil, errors.New(fmt.Sprintf("invalid path_pattern: %s", pathPattern))
		}
		methodStr = pathPattern[0:index]
		pathStr = pathPattern[index+1:]
	}

	path = strings.TrimSpace(pathStr)

	methodStr = strings.TrimSpace(methodStr)
	var methods []string
	if methodStr != "" {
		mArray := strings.Split(methodStr, MethodSeparator)
		if len(mArray) > 0 {
			for _, m := range mArray {
				tm := strings.TrimSpace(m)
				methods = append(methods, strings.ToLower(tm))
			}
		}
	}

	return path, methods, nil
}

func getRequestPath(param *AccessParam) string {
	path, _ := param.Metadata[AccessMetadataPath].(string)
	return path
}

func getRequestMethod(param *AccessParam) string {
	method, _ := param.Metadata[AccessMetadataMethod].(string)
	return method
}

type metadataStoreWhitelistRequestPlugin struct {
	MetadataRequestWhitelistKey string
}

func NewMetadataStoreWhitelistRequestPlugin(metadataRequestWhitelistKey string) (RequestPlugin, error) {
	if metadataRequestWhitelistKey == "" {
		return nil, errors.New("metadataRequestWhitelistKey is blank")
	}
	return &metadataStoreWhitelistRequestPlugin{
		MetadataRequestWhitelistKey: metadataRequestWhitelistKey,
	}, nil
}

func (d *metadataStoreWhitelistRequestPlugin) ReleaseRequest(ctx context.Context, param *AccessParam) (*RequestRet, error) {
	whitelistI, ok := param.Metadata[d.MetadataRequestWhitelistKey]
	if !ok {
		return nil, errors.New("not set whitelist key in metadata")
	}
	whitelist, ok := whitelistI.([]string)
	if !ok {
		return nil, errors.New("whitelist type is not []string")
	}
	if whitelist == nil || len(whitelist) == 0 {
		return &RequestRet{
			Released: false,
			Message:  "white list is empty",
		}, nil
	}
	path := getRequestPath(param)
	method := getRequestMethod(param)
	for _, wl := range whitelist {
		if wl == "" {
			continue
		}
		wlPath, wlMethods, err := parsePathPattern(wl)
		if err != nil {
			tilog.Warn(ctx, fmt.Sprintf("parse whitelist error: %s", wl), zap.Any("err", err))
			continue
		}
		wlExactMatchPath := fmt.Sprintf(ExactMatchFormatter, wlPath)
		_, err = regexp.Compile(wlExactMatchPath)
		if err != nil {
			tilog.Warn(ctx, fmt.Sprintf("whitelist path invalid: %s", wl), zap.Any("err", err))
			continue
		}
		if !regexp.MustCompile(wlExactMatchPath).MatchString(path) {
			continue
		}
		if len(wlMethods) == 0 {
			return &RequestRet{
				Released: true,
				Message:  fmt.Sprintf("request %s:%s hit white list %s", method, path, wl),
			}, nil
		}
		lm := strings.ToLower(method)
		for _, wlm := range wlMethods {
			lwlm := strings.ToLower(wlm)
			if lm == lwlm {
				return &RequestRet{
					Released: true,
					Message:  fmt.Sprintf("request %s:%s hit white list %s", method, path, wl),
				}, nil
			}
		}
	}
	return &RequestRet{
		Released: false,
		Message:  "not hit white list",
	}, nil
}
