package auth

import (
	"context"
	"fmt"
	"strings"
)

type CascadeResourceMatchFunc func(ctx context.Context, curResource, childResource string) (bool, error)

type CascadeResourceTree struct {
	ResourceType  string
	MatchFunc     CascadeResourceMatchFunc
	ChildResource *CascadeResourceTree
}

type treedResourceCascadePlugin struct {
	cascadeResourceRoot *CascadeResourceTree
}

func NewTreedResourceCascadePlugin(cascadeResourceRoot *CascadeResourceTree) ResourceCascadePlugin {
	return &treedResourceCascadePlugin{
		cascadeResourceRoot: cascadeResourceRoot,
	}
}

func (h *treedResourceCascadePlugin) CheckCascadeResource(ctx context.Context, param *ResourceCascadeParam) (*ResourceCascadeRet, error) {
	if h.cascadeResourceRoot == nil {
		releaseRet := &ResourceCascadeRet{
			Matched: true,
			Message: "not set cascade resource root",
		}
		return releaseRet, nil
	}

	resourceTypes := h.getTreedCascadeResourceTypes(h.cascadeResourceRoot)
	resourceType2ValueMap := parseCascadeResourceValue(param.ReqUri, resourceTypes)

	curResource := h.cascadeResourceRoot
	for {
		if curResource.ChildResource == nil {
			break
		}
		childResource := curResource.ChildResource

		curResourceType := curResource.ResourceType
		childResourceType := childResource.ResourceType
		matchFunc := curResource.MatchFunc

		curResourceValue, found := resourceType2ValueMap[curResourceType]
		if !found {
			curResource = childResource
			continue
		}
		childResourceValue, found := resourceType2ValueMap[childResourceType]
		if !found {
			curResource = childResource
			continue
		}
		matched, err := matchFunc(ctx, curResourceValue, childResourceValue)
		if err != nil {
			return nil, err
		}
		if !matched {
			interceptRet := &ResourceCascadeRet{
				Matched: false,
				Message: fmt.Sprintf("%s:%s not match cascade resource %s:%s", curResourceType, curResourceValue, childResourceType, childResourceValue),
			}
			return interceptRet, nil
		}
		curResource = childResource
	}

	releaseRet := &ResourceCascadeRet{
		Matched: true,
		Message: "cascade resource matched",
	}
	return releaseRet, nil
}

func (h *treedResourceCascadePlugin) getTreedCascadeResourceTypes(cascadeResourceRoot *CascadeResourceTree) []string {
	var resourceTypeMap = map[string]struct{}{}
	curResource := cascadeResourceRoot
	for {
		if curResource == nil {
			break
		}
		resourceTypeMap[curResource.ResourceType] = struct{}{}
		curResource = curResource.ChildResource
	}
	var resourceTypes []string
	for resourceType := range resourceTypeMap {
		resourceTypes = append(resourceTypes, resourceType)
	}
	return resourceTypes
}

type mappedResourceCascadePlugin struct {
	// curResourceType -> (childResourceType -> matchFunc)
	cascadeResourceMatchGroup map[string]map[string]CascadeResourceMatchFunc
}

func NewMappedResourceCascadeChecker(cascadeResourceMatchGroup map[string]map[string]CascadeResourceMatchFunc) ResourceCascadePlugin {
	return &mappedResourceCascadePlugin{
		cascadeResourceMatchGroup: cascadeResourceMatchGroup,
	}
}

func (h *mappedResourceCascadePlugin) CheckCascadeResource(ctx context.Context, param *ResourceCascadeParam) (*ResourceCascadeRet, error) {
	if len(h.cascadeResourceMatchGroup) == 0 {
		releaseRet := &ResourceCascadeRet{
			Matched: true,
			Message: "not set resource match group",
		}
		return releaseRet, nil
	}

	resourceTypes := h.getMappedCascadeResourceTypes(h.cascadeResourceMatchGroup)
	resourceType2ValueMap := parseCascadeResourceValue(param.ReqUri, resourceTypes)

	for curResourceType, childResourceTypeMatchFuncGroup := range h.cascadeResourceMatchGroup {
		curResourceValue, found := resourceType2ValueMap[curResourceType]
		if !found {
			continue
		}
		for childResourceType, matchFunc := range childResourceTypeMatchFuncGroup {
			childResourceValue, found := resourceType2ValueMap[childResourceType]
			if !found {
				continue
			}
			matched, err := matchFunc(ctx, curResourceValue, childResourceValue)
			if err != nil {
				return nil, err
			}
			if !matched {
				interceptRet := &ResourceCascadeRet{
					Matched: false,
					Message: fmt.Sprintf("%s:%s not match cascade resource %s:%s", curResourceType, curResourceValue, childResourceType, childResourceValue),
				}
				return interceptRet, nil
			}
		}
	}

	releaseRet := &ResourceCascadeRet{
		Matched: true,
		Message: "cascade resource matched",
	}

	return releaseRet, nil
}

func (h *mappedResourceCascadePlugin) getMappedCascadeResourceTypes(cascadeResourceMatchGroup map[string]map[string]CascadeResourceMatchFunc) []string {
	var resourceTypeMap = map[string]struct{}{}
	for curResourceType, childResourceTypeMatchFuncGroup := range cascadeResourceMatchGroup {
		resourceTypeMap[curResourceType] = struct{}{}
		for childResourceType := range childResourceTypeMatchFuncGroup {
			resourceTypeMap[childResourceType] = struct{}{}
		}
	}
	var resourceTypes []string
	for resourceType := range resourceTypeMap {
		resourceTypes = append(resourceTypes, resourceType)
	}
	return resourceTypes
}

func parseCascadeResourceValue(resourceUri string, resourceTypes []string) map[string]string {
	resourceSlice := strings.Split(resourceUri, ResourceValueSeparator)
	length := len(resourceSlice)

	var resourceTypeMap = map[string]struct{}{}
	for _, resourceType := range resourceTypes {
		resourceTypeMap[resourceType] = struct{}{}
	}

	var resourceType2ValueMap = map[string]string{}
	for i := 0; i < length; i++ {
		str := resourceSlice[i]
		_, ok := resourceTypeMap[str]
		if ok && i+1 < length {
			resourceType2ValueMap[str] = resourceSlice[i+1]
		}
	}
	return resourceType2ValueMap
}
