package config

import (
	"fmt"
	"regexp"
	"strings"

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

// rbac const
var RBACPathPattern2ResourceMap = map[string]string{

	// integrations
	"post:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/integrations":          "orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/integrations/create",
	"get:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/integrations":           "orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/integrations/list",
	"get:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/integrations/(\\d+)":    "orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/integrations/get",
	"patch:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/integrations/(\\d+)":  "orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/integrations/update",
	"delete:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/integrations/(\\d+)": "orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/integrations/delete",
}

type RBACConfig struct {
	Switch                      bool                      `mapstructure:"switch" default:"false"`
	RBACPathPattern2ResourceMap map[string]string         `mapstructure:"rbacPathPattern2ResourceMap"`
	PathPattern2ResourceMap     map[*regexp.Regexp]string `mapstructure:"-"`
	RBACPathWhitelist           []string                  `mapstructure:"rbacPathWhitelist"`
}

func (r *RBACConfig) Init() error {
	if err := r.initRbacPathWhiteList(); err != nil {
		return err
	}
	return r.initPathPattern2ResourceMap()
}

func (r *RBACConfig) initRbacPathWhiteList() error {
	if len(r.RBACPathWhitelist) == 0 {
		return nil
	}
	for _, p := range r.RBACPathWhitelist {
		_, err := regexp.Compile(p)
		if err != nil {
			return errors.Annotatef(err, "whitelist path:%s invalid", p)
		}
	}
	return nil
}

func (r *RBACConfig) GetRBACResource(path string, method string) string {
	method = strings.ToLower(method)
	path = strings.ToLower(path)
	for p, uri := range r.PathPattern2ResourceMap {
		if p.MatchString(strings.ToLower(fmt.Sprintf("%s:%s", method, path))) {
			return uri
		}
	}
	return ""
}

func (r *RBACConfig) initPathPattern2ResourceMap() error {
	if r.RBACPathPattern2ResourceMap == nil || len(RBACPathPattern2ResourceMap) == 0 {
		return errors.New("resourceMap is empty")
	}
	var formatResourceMap = map[*regexp.Regexp]string{}
	if err := r.covertExprToRegexp(formatResourceMap, r.RBACPathPattern2ResourceMap); err != nil {
		return err
	}
	if err := r.covertExprToRegexp(formatResourceMap, RBACPathPattern2ResourceMap); err != nil {
		return err
	}
	r.PathPattern2ResourceMap = formatResourceMap
	return nil
}

func (r *RBACConfig) covertExprToRegexp(
	formatResourceMap map[*regexp.Regexp]string,
	exprMap map[string]string,
) error {
	for pathExpr, resourceExpr := range exprMap {
		pathExpr = strings.ToLower(pathExpr)
		pathPattern, method := r.parsePathPattern(pathExpr)
		if len(method) == 0 || len(pathPattern) == 0 {
			return errors.New("pathPattern or method is empty")
		}
		// pathPattern: /api/v1/orgs/(\d+)/poc-applications
		// resourceExpr: /orgs/{org_id}/owner
		pathExprMatchExpr := fmt.Sprintf(common.EXACT_MATCH_FORMATTER, pathExpr)
		// check the pathPattern valid
		reg, err := regexp.Compile(pathExprMatchExpr)
		if err != nil {
			return err
		}
		formatResourceMap[reg] = resourceExpr
	}
	return nil
}

func (r *RBACConfig) parsePathPattern(pathPattern string) (path, method string) {
	split := strings.Split(pathPattern, common.PATH_METHOD_SEPERATOR)
	if len(split) == 1 {
		path = split[0]
		return path, method
	}
	if len(split) == 2 {
		method = split[0]
		path = split[1]
		return path, method
	}
	method = split[0]
	path = strings.Join(split[1:], common.PATH_METHOD_SEPERATOR)
	return method, path

}

func (r *RBACConfig) IsIgnoreRBACPath(path string) bool {
	if len(r.RBACPathWhitelist) == 0 {
		return false
	}
	for _, p := range r.RBACPathWhitelist {
		reg := regexp.MustCompile(p)
		if reg.MatchString(path) {
			return true
		}
	}
	return false
}
