package helperimpl

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

	"github.com/tidbcloud/tiinf/sdk/rbac/common"
)

var DefaultMetadataStoreResourceHelper = NewMetadataStoreResourceHelper()

type mapStoreResourceHelper struct {
	// k: path_pattern
	// v: resource_uri
	resourceMap map[string]string
}

func NewMapStoreResourceHelper(resourceMap map[string]string) common.ResourceHelper {
	formatResourceMap, err := validateAndFormatResourceMap(resourceMap)
	if err != nil {
		panic(err.Error())
	}
	return &mapStoreResourceHelper{
		resourceMap: formatResourceMap,
	}
}

func NewMapStoreResourceFunc(resourceMap map[string]string) common.GetResourceFunc {
	helper := NewMapStoreResourceHelper(resourceMap)
	return func(ctx context.Context, param *common.AccessParam) (string, error) {
		return helper.GetResource(ctx, param)
	}
}

func (d *mapStoreResourceHelper) GetResource(_ context.Context, req *common.AccessParam) (string, error) {
	if d.resourceMap == nil || len(d.resourceMap) == 0 {
		return "", nil
	}
	path := common.GetRequestPath(req)
	method := common.GetRequestMethod(req)
	for pathExpr, resourceExpr := range d.resourceMap {
		matched, resource, err := d.matchPath(pathExpr, path, method, resourceExpr)
		if err != nil {
			return "", err
		}
		if matched {
			return resource, nil
		}
	}
	return "", nil
}

func (d *mapStoreResourceHelper) matchPath(pathExpr, path, method, resourceExpr string) (bool, string, error) {
	// local set, must be right, so ignore err
	p, m, _ := common.ParsePathPattern(pathExpr)
	// method need match and method not match
	if m != common.LOCAL_DEFAULT_MOTHOD && m != strings.ToLower(method) {
		return false, "", nil
	}
	compile := regexp.MustCompile(p)
	// check whether path match the pathExpr
	matched := compile.MatchString(path)
	if !matched {
		return false, "", nil
	}
	// find path groups
	pathGroups := compile.FindStringSubmatch(path)
	resourceGroups := regexp.MustCompile(common.RESOURCE_EXPR_NO).FindAllString(resourceExpr, -1)
	// pathGroups[0] is the exact match group
	if len(pathGroups)-1 != len(resourceGroups) {
		return false, "", errors.New("mapStoreResourceHelper: resource/path variable count not match")
	}
	var retResource = resourceExpr
	for i := 1; i < len(pathGroups); i++ {
		retResource = strings.Replace(retResource, resourceGroups[i-1], pathGroups[i], 1)
	}
	return true, retResource, nil
}

func validateAndFormatResourceMap(resourceMap map[string]string) (map[string]string, error) {
	if resourceMap == nil || len(resourceMap) == 0 {
		return nil, errors.New("resourceMap is empty")
	}
	var formatResourceMap = map[string]string{}
	for pathExpr, resourceExpr := range resourceMap {
		pathPattern, method, err := common.ParsePathPattern(pathExpr)
		if err != nil {
			return nil, err
		}
		// pathPattern: /api/v1/orgs/(\d+)/poc-applications
		// resourceExpr: /orgs/{org_id}/owner
		pathExactMatchExpr := fmt.Sprintf(common.EXACT_MATCH_FORMATTER, pathPattern)
		// check the pathPattern valid
		_, err = regexp.Compile(pathExactMatchExpr)
		if err != nil {
			return nil, err
		}
		// check the variable count in pathPattern and resourceExpr
		pathGroups := regexp.MustCompile(common.PATH_EXPR_NO).FindAllString(pathPattern, -1)
		resourceGroups := regexp.MustCompile(common.RESOURCE_EXPR_NO).FindAllString(resourceExpr, -1)
		if len(pathGroups) != len(resourceGroups) {
			return nil, errors.New("variable count not match in path_patten and resource_uri")
		}
		if method == "" {
			method = common.LOCAL_DEFAULT_MOTHOD
		}
		formatter := fmt.Sprintf(common.PATH_METHOD_FORMATTER, strings.ToLower(method), pathExactMatchExpr)
		formatResourceMap[formatter] = resourceExpr
	}
	return formatResourceMap, nil
}

type metadataStoreResourceHelper struct {
}

func NewMetadataStoreResourceHelper() common.ResourceHelper {
	return &metadataStoreResourceHelper{}
}

func (d *metadataStoreResourceHelper) GetResource(_ context.Context, req *common.AccessParam) (string, error) {
	mResource, ok := req.Metadata[common.METADATA_RESOURCE]
	if !ok {
		return "", errors.New("metadataStoreResourceHelper: resource not found int metadata")
	}
	resource, ok := mResource.(string)
	if !ok {
		return "", errors.New("metadataStoreResourceHelper: resource type not string")
	}

	resourceGroups := regexp.MustCompile(common.RESOURCE_EXPR_NO).FindAllString(resource, -1)
	if resourceGroups != nil && len(resourceGroups) > 0 {
		mParams, ok := req.Metadata[common.METADATA_RESOURCE_PARAM]
		if !ok {
			return "", errors.New("metadataStoreResourceHelper: resource params not found int metadata")
		}
		params, ok := mParams.(map[string]string)
		if !ok {
			return "", errors.New("metadataStoreResourceHelper: resource params type not map[string]string")
		}
		// find the variable in params and replace
		for _, group := range resourceGroups {
			variable := group[1 : len(group)-1]
			value, ok := params[variable]
			if !ok {
				return "", errors.New("metadataStoreResourceHelper: resource variable not found value in resource params")
			}
			resource = strings.Replace(resource, group, value, 1)
		}
	}

	return resource, nil
}
