package rbac

import (
	"context"
	"errors"

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

type regionPermissionChecker struct {
	opened           bool
	identityH        common.IdentityHelper
	resourceH        common.RegionResourceHelper
	permissionH      common.RegionPermissionHelper
	requestHs        []common.RequestHelper
	resourceCascadeH common.ResourceCascadeChecker
}

func NewRegionPermissionChecker(
	identityH common.IdentityHelper,
	resourceH common.RegionResourceHelper,
	permissionH common.RegionPermissionHelper) PermissionChecker {

	if identityH == nil {
		panic("IdentityHelper is nil")
	}
	if resourceH == nil {
		panic("ResourceHelper is nil")
	}
	if permissionH == nil {
		panic("RegionPermissionHelper is nil")
	}

	handler := regionPermissionChecker{
		opened:      true,
		identityH:   identityH,
		resourceH:   resourceH,
		permissionH: permissionH,
		requestHs:   []common.RequestHelper{},
	}

	return &handler
}

func (r *regionPermissionChecker) SetOpened(opened bool) {
	r.opened = opened
}

func (r *regionPermissionChecker) RegisterRequestHelper(requestH common.RequestHelper) {
	if requestH != nil {
		r.requestHs = append(r.requestHs, requestH)
	}
}

func (r *regionPermissionChecker) SetResourceCascadeChecker(resourceCascadeH common.ResourceCascadeChecker) {
	if resourceCascadeH != nil {
		r.resourceCascadeH = resourceCascadeH
	}
}

func (r *regionPermissionChecker) Handle(ctx context.Context, param *common.AccessParam) (*common.AccessResult, error) {
	if param == nil || param.Metadata == nil {
		return nil, errors.New("access param invalid")
	}
	if !r.opened {
		return release("permission check switch not open"), nil
	}
	released, err := r.whetherReleaseRequest(ctx, param)
	if err != nil {
		return nil, err
	}
	if released {
		return release("hit release request white list"), nil
	}
	identity, err := r.identityH.GetIdentity(ctx, param)
	if err != nil {
		return nil, err
	}
	resource, appID, err := r.resourceH.GetResourceAndAppID(ctx, param)
	if err != nil {
		return nil, err
	}
	// req path not match path pattern
	if resource == "" {
		return pathNotMathWithData("path not match", identity, resource), nil
	}
	if appID == "" {
		return pathNotMathWithData("appID not found", identity, resource), nil
	}

	matched, msg, err := whetherCascadeResourceMatch(ctx, r.resourceCascadeH, resource)
	if err != nil {
		return nil, err
	}
	if !matched {
		return interceptWithData(msg, identity, resource), nil
	}

	hasPermit, err := r.permissionH.HasPermission(ctx, identity, resource, appID)
	if err != nil {
		return nil, err
	}
	if hasPermit {
		return releaseWithData("identity permission check passed", identity, resource), nil
	}
	return interceptWithData("identity has no permission", identity, resource), nil

}

func (r *regionPermissionChecker) whetherReleaseRequest(ctx context.Context, param *common.AccessParam) (bool, error) {
	if r.requestHs != nil && len(r.requestHs) > 0 {
		for _, helper := range r.requestHs {
			released, err := helper.ReleaseRequest(ctx, param)
			if err != nil {
				return false, err
			}
			if released {
				return true, nil
			}
		}
	}
	return false, nil
}

func GetRegionPermissionChecker(
	identityH common.IdentityHelper,
	resourceH common.RegionResourceHelper,
	permissionH common.RegionPermissionHelper,
	resourceCascadeH common.ResourceCascadeChecker,
	requestHs ...common.RequestHelper) PermissionChecker {
	onceCheckerInit.Do(func() {
		onceChecker = initRegionPermissionChecker(identityH, resourceH, permissionH, resourceCascadeH, requestHs...)
	})
	if onceChecker == nil {
		panic("PermissionChecker is nil")
	}
	return onceChecker
}

func initRegionPermissionChecker(
	identityH common.IdentityHelper,
	resourceH common.RegionResourceHelper,
	permissionH common.RegionPermissionHelper,
	resourceCascadeH common.ResourceCascadeChecker,
	requestHs ...common.RequestHelper) PermissionChecker {
	checker := NewRegionPermissionChecker(identityH, resourceH, permissionH)
	if resourceCascadeH != nil {
		checker.SetResourceCascadeChecker(resourceCascadeH)
	}
	if requestHs != nil && len(requestHs) > 0 {
		for _, h := range requestHs {
			checker.RegisterRequestHelper(h)
		}
	}
	return checker
}
