package server

import (
	"context"
	"errors"
	"fmt"
	"time"

	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/duke-git/lancet/v2/slice"

	"github.com/tidbcloud/account/commons/config"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/rbac"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"
	"github.com/tidbcloud/tiinf/sdk/rbac/useadapter"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

var pathPatternResourceMap = map[string]string{
	"/api/v1/orgs/(\\d+)/poc-applications":                    "orgs/{org_id}/poc_application/create",
	"/api/v1/orgs/(\\d+)":                                     "orgs/{org_id}/update",
	"/api/v1/orgs/(\\d+)/default-payment":                     "orgs/{org_id}/default_payment/update",
	"/api/v1/orgs/(\\d+)/cn-policy":                           "orgs/{org_id}/cn_policy/acknowledge",
	"get:/api/v1/orgs/(\\d+)/users":                           "orgs/{org_id}/users/list",
	"get:/api/v1/orgs/(\\d+)/users/me":                        "orgs/{org_id}/my_user_profile/get",
	"put:/api/v1/orgs/(\\d+)/users/(\\d+)":                    "orgs/{org_id}/users/{user_id}/update",
	"put:/api/v1/orgs/(\\d+)/users/me":                        "orgs/{org_id}/my_user_profile/update",
	"delete:/api/v1/orgs/(\\d+)/users/(\\d+)":                 "orgs/{org_id}/users/{user_id}/delete",
	"post:/api/v1/orgs/(\\d+)/projects":                       "orgs/{org_id}/project/create",
	"get:/api/v1/orgs/(\\d+)/projects":                        "orgs/{org_id}/projects/list",
	"patch:/api/v1/orgs/(\\d+)/projects/(\\d+)":               "orgs/{org_id}/projects/{project_id}/update",
	"delete:/api/v1/orgs/(\\d+)/projects/(\\d+)":              "orgs/{org_id}/projects/{project_id}/delete",
	"get:/api/v1/orgs/(\\d+)/projects/(\\d+)/users":           "orgs/{org_id}/projects/{project_id}/users/list",
	"post:/api/v1/orgs/(\\d+)/projects/(\\d+)/users":          "orgs/{org_id}/projects/{project_id}/user/invite",
	"post:/api/v1/orgs/(\\d+)/users":                          "orgs/{org_id}/user/invite",
	"delete:/api/v1/orgs/(\\d+)/projects/(\\d+)/users/(\\d+)": "orgs/{org_id}/projects/{project_id}/users/{user_id}/delete",
	"patch:/api/v1/orgs/(\\d+)/projects/(\\d+)/users/(\\d+)":  "orgs/{org_id}/projects/{project_id}/users/{user_id}/update",
	"post:/api/v1/orgs/(\\d+)/api_keys":                       "orgs/{org_id}/api_key/create",
	"get:/api/v1/orgs/(\\d+)/api_keys":                        "orgs/{org_id}/api_keys/list",
	"get:/api/v1/orgs/(\\d+)/projects/(\\d+)/api_keys":        "orgs/{org_id}/projects/{project_id}/api_keys/list",
	"patch:/api/v1/orgs/(\\d+)/api_keys/(\\d+)":               "orgs/{org_id}/api_keys/{api_key}/update",
	"delete:/api/v1/orgs/(\\d+)/api_keys/(\\d+)":              "orgs/{org_id}/api_keys/{api_key}/delete",
	"/api/v1/orgs/(\\d+)/bind_third_party":                    "orgs/{org_id}/bind_third_party",

	"post:/api/v1/orgs/(\\d+)/projects/(\\d+)/api_keys":          "orgs/{org_id}/projects/{project_id}/api_key/create",
	"patch:/api/v1/orgs/(\\d+)/projects/(\\d+)/api_keys/(\\d+)":  "orgs/{org_id}/projects/{project_id}/api_keys/{api_key}/update",
	"delete:/api/v1/orgs/(\\d+)/projects/(\\d+)/api_keys/(\\d+)": "orgs/{org_id}/projects/{project_id}/api_keys/{api_key}/delete",

	"/api/v1/orgs/(\\d+)/scim-groups":                  "account/orgs/{org_id}/list_scim_groups",
	"/api/v1/orgs/(\\d+)/scim-groups/\\d+/update-role": "account/orgs/{org_id}/update_scim_group_role",
	"/api/v1/orgs/(\\d+)/bearer-token":                 "account/orgs/{org_id}/create_bearer_token",

	// api_key relate resource_uri
	"post:/api/v1/api-keys":                      "account/api_keys/create",
	"patch:/api/v1/api-keys/(\\d+)":              "account/api_keys/{api_key_id}/update",
	"delete:/api/v1/api-keys/(\\d+)":             "account/api_keys/{api_key_id}/delete",
	"get:/api/v1/api-keys":                       "account/api_keys/list",
	"/api/v1/api-keys/(\\d+)/permission/grant":   "account/api_keys/{api_key_id}/grant_permission",
	"/api/v1/api-keys/(\\d+)/permission/recover": "account/api_keys/{api_key_id}/recover_permission",
	"/api/v1/api-keys/(\\d+)/resource":           "account/api_keys/{api_key_id}/list_ref_resource",
	"/api/v1/api-keys/list-by-resource":          "account/api_keys/list_keys_by_ref_resource",

	"/api/v1/org/(\\w+)":                           "account/org/{company_name}/get",
	"/api/v1/orgs/(\\d+)/opensso":                  "account/orgs/{org_id}/opensso",
	"/api/v1/orgs/(\\d+)/auth-method/update":       "account/orgs/{org_id}/auth_method/update",
	"/api/v1/orgs/(\\d+)/auth-method/status":       "account/orgs/{org_id}/auth_method/status",
	"/api/v1/orgs/(\\d+)/auth-method/list":         "account/orgs/{org_id}/auth_method/list",
	"/api/v1/orgs/(\\d+)/auth-method/email-domain": "account/orgs/{org_id}/auth_method/email_domain",
	"/api/v1/orgs/(\\d+)/auth-method":              "account/orgs/{org_id}/auth_method/show",

	//sql user
	"post:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sql-user/create": "account/central/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sql-user/create",
	"get:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sql-user/list":    "account/central/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sql-user/list",
	"put:/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sql-user/update":  "account/central/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sql-user/update",
	"/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sql-user/delete":      "account/central/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sql-user/delete",
	"/api/v1/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sql-user/query":       "account/central/orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sql-user/query",

	// jsm
	"get:/api/v1/orgs/(\\d+)/support/preload": "/api/v1/orgs/{org_id}/support/preload",
	"post:/api/v1/orgs/(\\d+)/support/create": "/api/v1/orgs/{org_id}/support/create",
	"post:/upload/orgs/(\\d+)/support-attach": "/upload/orgs/${org_id}/support-attach",

	// Msp
	"get:/api/v1/orgs/(\\d+)/msp/msp-customer": "/api/v1/orgs/{org_id}/msp/msp-customer",

	"/api/v1/orgs/(\\d+)/partner": "account/orgs/{org_id}/get_customer_belong_partner_config",

	// one console: user mgmt
	"get:/api/v1/orgs/(\\d+)/console-users":                           "account/orgs/{org_id}/console_user/list",
	"post:/api/v1/orgs/(\\d+)/console-user/assign-role":               "account/orgs/{org_id}/console_user/assign_role",
	"post:/api/v1/orgs/(\\d+)/console-user/edit-role":                 "account/orgs/{org_id}/console_user/edit_role",
	"post:/api/v1/orgs/(\\d+)/console-user/check-email-assigned-role": "account/orgs/{org_id}/console_user/assign_role",

	// one console: api key
	"post:/api/v1/orgs/(\\d+)/console-apikey/create": "account/orgs/{org_id}/console_apikey/create",
	"put:/api/v1/orgs/(\\d+)/console-apikey/update":  "account/orgs/{org_id}/console_apikey/update",
	"get:/api/v1/orgs/(\\d+)/console-apikeys":        "account/orgs/{org_id}/console_apikey/list",

	// domain
	"post:/api/v1/orgs/(\\d+)/domains":             "account/orgs/{org_id}/add_domain",
	"post:/api/v1/orgs/(\\d+)/domains/\\d+/verify": "account/orgs/{org_id}/verify_domain",
	"post:/api/v1/orgs/(\\d+)/domains/confirm":     "account/orgs/{org_id}/confirm_domain",
	"get:/api/v1/orgs/(\\d+)/domains":              "account/orgs/{org_id}/list_domains",
	"delete:/api/v1/orgs/(\\d+)/domains/\\d+":      "account/orgs/{org_id}/delete_domain",
}

var pathWhitelist = []string{
	"/api/v1/auth/meta",
	"/api/v1/auth/verify-email-password",
	"/api/v1/auth/verify-otp-code",
	"/api/v1/auth/verify-recovery-code",
	"/api/v1/auth/start-enable-mfa",
	"/api/v1/auth/confirm-mfa-authenticators",
	"/api/v1/auth/disable-mfa",
	"/api/v1/audit_log/auth0",
	"/api/v1/audit_log/payment_method",
	"/api/v1/orgs",
	"/api/v1/orgs/verification-email",
	"/api/v1/orgs/\\d+/acception",
	"/api/v1/orgs/\\d+/auth0-user",
	"/api/v1/orgs/\\d+/access-logs",
	"/api/v1/my-ip",
	"/api/v1/user-roles",
	"/api/v1/scope-users",
	"/api/v1/orgs/\\d+/billing/setup-intent/confirm",
	"/api/v1/metrics/login-metrics",
	"/api/v1/orgs/validate_thirdparty_token",
	"/api/v1/login-user-roles",
	"/api/v1beta/projects",
	"/inner/account-central/.*",
	"/api/v1/auth-method",
	"/api/v1/orgs/\\d+/exp-whitelist",
	"/api/v1/message/notice",
	"/api/v1/user/history/user-visit",
	"/api/v1/user/me",
	"/api/v1/user/scope",
	"/api/v1/api-keys/msp",
	"/api/v1/auth-method/valid-check", // for enterprise connection fast login page to check connection name valid
	"/api/v1/msp/msp-config",
	"/api/v1/msp/services-agreement", // no org_id in path, add rbac for create agreement in biz logic
	"/api/v1/user/user-ext",
	"/api/v1/user/plg-on-boarding-survey/create",
	"/api/v1/orgs/\\d+/config_feature_flag_for_provider_source",
	"/api/v1/user/revoke-login-state",    // user level
	"get:/api/v1/orgs/\\d+/org_projects", // for fe to show projects in org
}

var localPathWhitelist = []string{
	"/api/v1/active-auth-methods",
	"/api/v1/orgs/.*/opensso",
	"/api/v1/orgs/.*/auth-method.*",
	"/api/v1/user/privileges",
	"/api/v1/.*",
	"/api/v1/auth-method/valid-check",
	"/api/v1/orgs/.*/projects/.*/clusters/.*/sql-user/.*",
}

func init() {
	helperimpl.DefaultCachedPermissionHelper.SetReadTimeout(6 * time.Second)
}

func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		logger := tilog.WithContextL(ctx)
		if !useadapter.WhetherRequestComeFromGrpcGateway(ctx) {
			logger.Info("rbac sdk permission check ignore, request not come from grpc gateway")
			return handler(ctx, req)
		}
		checker := getPermissionChecker()
		param := useadapter.GenAccessParam4GrpcGateway(ctx)
		result, err := checker.Handle(ctx, param)
		logger.Info("rbac sdk permission check finished", zap.Any("result", result), zap.Any("err", err), zap.Any("param", param))

		if IsCloseRbacAccessCheck() {
			logger.Info("rbac sdk permission check closed, do not check result")
			return handler(ctx, req)
		}

		if err != nil {
			return nil, status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return nil, status.Error(codes.PermissionDenied, "permission denied")
		}

		return handler(ctx, req)
	}
}

func StreamServerInterceptor() grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		logger := tilog.WithContextL(ss.Context())
		if !useadapter.WhetherRequestComeFromGrpcGateway(ss.Context()) {
			logger.Info("rbac sdk permission check ignore, request not come from grpc gateway")
			return handler(srv, ss)
		}
		checker := getPermissionChecker()
		param := useadapter.GenAccessParam4GrpcGateway(ss.Context())
		result, err := checker.Handle(ss.Context(), param)
		logger.Info("rbac sdk permission check finished", zap.Any("result", result), zap.Any("err", err), zap.Any("param", param))

		if IsCloseRbacAccessCheck() {
			logger.Info("rbac sdk permission check closed, do not check result")
			return handler(srv, ss)
		}

		if err != nil {
			return status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(srv, ss)
	}
}

func getPermissionChecker() rbac.PermissionChecker {
	if config.IsLocalEnv() {
		pathWhitelist = slice.Concat(pathWhitelist, localPathWhitelist)

	}

	return rbac.GetPermissionCheckerWithCommonParam(&rbac.PermissionCheckerCommonParam{
		IdentityF:               getIdentityFunc,
		PathPattern2ResourceMap: pathPatternResourceMap,
		PathWhitelist:           pathWhitelist,
	})
}

func getIdentityFunc(ctx context.Context, _ *common.AccessParam) (string, error) {
	u, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return "", errors.New("get user from context error")
	}
	return fmt.Sprintf("%d", u.User.ID), nil
}

func IsCloseOriginAccessCheck() bool {
	return true
}

func IsCloseRbacAccessCheck() bool {
	return false
}
