package access

import (
	"context"
	"fmt"

	tokenSvc "github.com/tidbcloud/account/internel/svc/token"

	"github.com/tidbcloud/pkgv2/clusterproxy"

	"github.com/tidbcloud/account/internel/controllers/acl/access/transformer"
	apikey2 "github.com/tidbcloud/account/internel/svc/apikey"
	"github.com/tidbcloud/account/internel/svc/authn"
	"github.com/tidbcloud/account/internel/svc/dbjwt"
	"github.com/tidbcloud/account/internel/svc/dbuser"
	"github.com/tidbcloud/account/internel/svc/org"
	"github.com/tidbcloud/account/internel/svc/rbac"
	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao/project"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"

	"github.com/tidbcloud/account/central/utils"

	"github.com/tidbcloud/account/thirdparty/infra"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/tiinf/errs"
	"gorm.io/gorm"

	"github.com/tidbcloud/account/idl/pbgen/proto/acl"
	"github.com/tidbcloud/account/internel/dao/entity"
	"go.uber.org/zap"
)

type accessService struct {
	acl.UnimplementedAccessServiceServer
	authn.OperationsService
	dbUserSvc      dbuser.DBUserIface
	userSvc        usersvc.UserSvcIface
	tokenSvc       tokenSvc.TokenSvcIface
	apiKeySvc      apikey2.ApiKeySvcIface
	rbacSvc        rbac.RbacUserSvcIface
	roleBindingSvc rbac.RbacRoleBindingIface
}

type getApiKeyDBUserAndJwtResp struct {
	dbUser          *string
	jwt             *string
	isDedicated     *bool
	serverlessToken *string
	clusterId       *uint64
	orgId           *uint64
}

// NewService constructs an AccessServiceServer
func NewService(s authn.OperationsService, dbUserSvc dbuser.DBUserIface, userSvc usersvc.UserSvcIface, tokenSvc tokenSvc.TokenSvcIface, rbacSvc rbac.RbacUserSvcIface, apiKeySvc apikey2.ApiKeySvcIface, rbacRoleBindingSvc rbac.RbacRoleBindingIface) acl.AccessServiceServer {
	return &accessService{
		OperationsService: s,
		dbUserSvc:         dbUserSvc,
		userSvc:           userSvc,
		tokenSvc:          tokenSvc,
		rbacSvc:           rbacSvc,
		apiKeySvc:         apiKeySvc,
		roleBindingSvc:    rbacRoleBindingSvc,
	}
}

// AuthFuncOverride disables the default auth0 interceptor. See:
// https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.go#L26
func (a *accessService) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
	return ctx, nil
}

func (a *accessService) GetOpenAPIAccess(ctx context.Context, req *acl.GetOpenAPIAccessReq) (*acl.GetOpenAPIAccessResp, error) {
	apiKey, err := a.apiKeySvc.GetApiKeyByAccessKey(ctx, req.PublicKey)
	if err != nil {
		return nil, err
	}
	if apiKey == nil {
		return nil, errs.NewBadRequest("api key not found", nil)
	}
	param := apikey2.GetResourceInfoByApiKeyIDParam{
		ApiKeyId: apiKey.Id,
	}
	resourceInfos, e := a.apiKeySvc.ListResourceInfosByApiKeyID(ctx, &param)
	if e != nil {
		return nil, e
	}
	var orgResource *apikey2.ApiKeyResourceInfo
	for _, resourceInfo := range resourceInfos.Items {
		if resourceInfo.ScopeType == entity.RoleBindingScopeOrg.String() {
			orgResource = resourceInfo
			break
		}
	}
	if orgResource == nil {
		return nil, errs.NewBadRequest("api key not grant permission", nil)
	}
	if orgResource.Role == entity.RoleApiKeyOrgOwner {
		orgResource.Role = entity.RoleOrgOwner
	}
	decodeUserRole := entity.RoleBindingDecode(orgResource.Role)

	orgId := orgResource.ResourceId
	orgRole := string(decodeUserRole)

	// FIXME(iven): This is temporary until project level roles are supported, for now we assign owner role for all projects
	var projects []entity.Project
	err = database.Transaction(func(tx *gorm.DB) error {
		projects, err = project.GetActiveProjects(ctx, tx, entity.ID(orgId))
		return err
	})
	if err != nil {
		return nil, errs.NewInternalDB(fmt.Sprintf("Failed to query all projects of tenant %d", orgId), err)
	}

	var projectAccesses []*acl.ProjectAccessResp
	for _, project := range projects {
		projectAccesses = append(projectAccesses, &acl.ProjectAccessResp{
			Id:           project.ID.Uint64(),
			Roles:        []string{},
			ProjectRoles: []acl.ProjectRole{},
		})
	}

	resp := &acl.GetOpenAPIAccessResp{
		Org: &acl.OrgAccessResp{
			Id: orgId,
			Roles: []string{
				orgRole,
			},
			OrgRoles: []acl.OrgRole{
				transformer.OrgRoleMap[entity.UserRole(orgRole)],
			},
		},
		Projects: projectAccesses,
	}

	return resp, nil
}

func (a *accessService) HasAdminAccess(ctx context.Context, req *acl.HasAdminAccessReq) (*acl.HasAdminAccessResp, error) {
	userID := req.UserId
	var (
		user      *entity.User
		userInOrg bool
	)

	err := database.Transaction(func(tx *gorm.DB) error {
		var e error

		user, e = usersvc.GetUserByID(tx, entity.ID(userID))
		if e != nil {
			return e
		}

		userInOrg, e = org.IsUserInOrg(ctx, tx, user.ID, entity.PingCAPTenantID)
		if e != nil {
			return e
		}

		return nil
	})
	if err != nil {
		return nil, errs.NewInternalDB(err.Error(), err)
	}

	response := &acl.HasAdminAccessResp{
		HasAccess: userInOrg && rbac.HasPrivilege(ctx, user.Roles, req.Uri, req.Method),
	}

	return response, nil
}

func (a *accessService) GetJwtByToken(ctx context.Context, req *acl.GetJwtByTokenReq) (*acl.GetJwtByTokenResp, error) {
	//defer bm.OnExit(ctx)
	//bm := NewBenchmarkInfo("GetUserByToken", zap.String("token", req.Token)).Begin(ctx)
	// wlt test
	tilog.Info(ctx, "GetJwtByToken start")

	param := dbjwt.GetJwtByTokenParam{
		ClusterID:  req.ClusterId,
		Auth0Token: req.Auth0Token,
	}
	token, err := dbjwt.GlobalDBJwtSvc.GetJwtByToken(ctx, &param)
	if err != nil {
		return nil, err
	}

	response := &acl.GetJwtByTokenResp{
		Jwt:      token.Jwt,
		DbUser:   token.DbUser,
		BaseResp: &base.BaseResp{},
	}
	return response, nil

	//if req.Auth0Token == "" {
	//	tilog.Info(ctx, "Auth0Token is required", zap.Any("req", req))
	//	return nil, errs.NewBadRequest("Auth0Token is required", nil)
	//}
	//if req.ClusterId == 0 {
	//	tilog.Info(ctx, "ClusterId is required", zap.Any("req", req))
	//	return nil, errs.NewBadRequest("ClusterId is required", nil)
	//}
	//dbUserSvc := a.dbUserSvc
	//userSvc := a.userSvc
	//tokenSvc := a.tokenSvc
	//rbacSvc := a.rbacSvc
	//
	//email, err := tokenSvc.GetUserEmailByToken(ctx, req.Auth0Token)
	//if err != nil {
	//	tilog.Info(ctx, "failed to get user ", zap.Error(err))
	//	return nil, err
	//}
	//
	//user, err := userSvc.GetUserByEmail(ctx, email)
	//if err != nil {
	//	tilog.Info(ctx, "failed to get user by email", zap.Any("req", req), zap.Error(err))
	//	return nil, err
	//}
	//
	//dbUser, jwt, err := getDBUserAndJwt(ctx, req.ClusterId, user, dbUserSvc, rbacSvc)
	//if err != nil {
	//	return nil, err
	//}
	//
	//response := &acl.GetJwtByTokenResp{
	//	Jwt:    *jwt,
	//	DbUser: *dbUser,
	//}
	//return response, nil

}

func (a *accessService) GetJwtByApiKey(ctx context.Context, req *acl.GetJwtByApiKeyReq) (*acl.GetJwtByApiKeyResp, error) {
	tilog.Info(ctx, "GetJwtByApiKey start")
	param := dbjwt.GetJwtByApiKeyParam{
		ClusterID: req.ClusterId,
		AccessKey: req.AccessKey,
		ApiKeyID:  req.ApiKeyId,
	}
	token, err := dbjwt.GlobalDBJwtSvc.GetJwtByApiKey(ctx, &param)
	if err != nil {
		return nil, err
	}

	response := &acl.GetJwtByApiKeyResp{
		Jwt:      token.Jwt,
		DbUser:   token.DbUser,
		BaseResp: &base.BaseResp{},
	}
	return response, nil

	//if req.AccessKey == "" && req.ApiKeyId == 0 {
	//	tilog.Info(ctx, "access_key and api_key_id cannot be both empty", zap.Any("req", req))
	//	return nil, errs.NewBadRequest("access_key cannot be empty", nil)
	//}
	//if req.ClusterId == 0 {
	//	tilog.Info(ctx, "cluster_id is required", zap.Any("req", req))
	//	return nil, errs.NewBadRequest("cluster_id is required", nil)
	//}
	//var keyEntity *apikey.ApiKey
	//if req.AccessKey != "" {
	//	apiKey, err := a.apiKeySvc.GetApiKeyByAccessKey(ctx, req.AccessKey)
	//	if err != nil {
	//		tilog.Error(ctx, "failed to get api key by ak", zap.Error(err))
	//		return nil, errs.NewRecordExist("failed to get key , please check the ak", err)
	//	}
	//	keyEntity = apiKey
	//} else if req.ApiKeyId != 0 {
	//	apiKey, err := a.apiKeySvc.GetApiKeyByID(ctx, req.ApiKeyId)
	//	if err != nil {
	//		tilog.Error(ctx, "failed to get api key by id", zap.Error(err))
	//		return nil, errs.NewRecordExist("failed to get user , please check the id", err)
	//	}
	//	keyEntity = apiKey
	//}
	//if keyEntity == nil {
	//	tilog.Info(ctx, "api key not exist")
	//	return nil, errs.NewRecordExist("api key not exist", nil)
	//}
	////dbUser, jwt, err := getApiKeyDBUserAndJwt(ctx, req.ClusterId, keyEntity, a.dbUserSvc)
	//resp, err := getApiKeyDBUserAndJwt(ctx, req.ClusterId, keyEntity, a.dbUserSvc)
	//
	//if err != nil {
	//	return nil, err
	//}
	////  check dbuser
	//go a.checkAPIKeyDBuser(context.Background(), req.AccessKey, req.ClusterId, *resp)
	//
	//response := &acl.GetJwtByApiKeyResp{
	//	Jwt:      *resp.jwt,
	//	DbUser:   *resp.dbUser,
	//	BaseResp: &base.BaseResp{},
	//}
	//return response, nil
}

// (*string, *string, error)
//func getApiKeyDBUserAndJwt(ctx context.Context, clusterId uint64, apiKey *apikey.ApiKey, dbUserSvc dbuser.DBUserIface) (*getApiKeyDBUserAndJwtResp, error) {
//	cluster, err := infra.GetHandler().GetClusterById(ctx, clusterId, infra.SupportDBUserStatus...)
//	if err != nil {
//		tilog.Info(ctx, "call cm GetClusterById error", zap.Error(err))
//		return nil, err
//	}
//	if cluster == nil {
//		return nil, errs.NewBadRequest("clusterId is  unavailable", nil)
//	}
//	flag := infra.IsSupportDBUser(ctx, cluster)
//	if !flag {
//		return nil, errs.NewBadRequest("the cluster not support dbuser", nil)
//	}
//	tilog.Info(ctx, "the api key  have cluster permissions", zap.Any("access_key", apiKey.AccessKey), zap.Any("clusterId", clusterId))
//	isDedicated := !cluster.IsDevTier
//	getDBUserReq := utils.GetDBUserReq{
//		Email:           apiKey.AccessKey,
//		IsDedicated:     &isDedicated,
//		ServerlessToken: cluster.ServerlessToken,
//		TargetType:      entity.RoleBindingTargetAPIKey.String(),
//	}
//
//	dbUser, err := utils.GetDBUser(ctx, getDBUserReq)
//	if err != nil {
//		return nil, err
//	}
//	jwt, err := dbUserSvc.GetJwtToken(ctx, cluster.TenantId, "", *dbUser, clusterId, isDedicated)
//	if err != nil {
//		return nil, err
//	}
//	resp := getApiKeyDBUserAndJwtResp{
//		dbUser:          dbUser,
//		jwt:             &jwt,
//		isDedicated:     &isDedicated,
//		serverlessToken: cluster.ServerlessToken,
//		orgId:           &cluster.TenantId,
//	}
//
//	return &resp, nil
//}

func (a *accessService) GetJwtByUser(ctx context.Context, req *acl.GetJwtByUserReq) (*acl.GetJwtByUserResp, error) {
	bm := utils.Begin(ctx, "GetJwtByUser", zap.Any("email", req.Email), zap.Any("ClusterId", req.ClusterId))
	defer bm.OnExit(ctx)

	param := dbjwt.GetJwtByUserParam{
		ClusterID: req.ClusterId,
		UserID:    req.UserId,
		Email:     req.Email,
	}
	token, err := dbjwt.GlobalDBJwtSvc.GetJwtByUser(ctx, &param)
	if err != nil {
		return nil, err
	}

	response := &acl.GetJwtByUserResp{
		Jwt:      token.Jwt,
		DbUser:   token.DbUser,
		BaseResp: &base.BaseResp{},
	}
	return response, nil

	//if req.UserId == 0 && req.Email == "" {
	//	tilog.Info(ctx, "user_id and email cannot be both empty", zap.Any("req", req))
	//	return nil, errs.NewBadRequest("user_id and email cannot be both empty", nil)
	//}
	//
	//if req.ClusterId == 0 {
	//	tilog.Info(ctx, "cluster_id is required", zap.Any("req", req))
	//	return nil, errs.NewBadRequest("cluster_id is required", nil)
	//}
	//dbUserSvc := a.dbUserSvc
	//userSvc := a.userSvc
	//rbacSvc := a.rbacSvc
	//var err error
	//var user *entity.User
	//if req.Email != "" {
	//	user, err = userSvc.GetUserByEmail(ctx, req.Email)
	//	if err != nil {
	//		tilog.Info(ctx, "failed to get user by email", zap.Error(err))
	//		return nil, errs.NewRecordExist("failed to get user , please check the email", err)
	//	}
	//} else if req.UserId != 0 {
	//	user, err = userSvc.GetUserByID(ctx, req.UserId)
	//	if err != nil {
	//		tilog.Info(ctx, "failed to get user by userID", zap.Error(err))
	//		return nil, errs.NewRecordExist("failed to get user , please check the user_id", err)
	//	}
	//}
	//
	//if user == nil {
	//	tilog.Info(ctx, "failed to get user e, the user is not exit", zap.Error(err))
	//	return nil, errs.NewRecordExist("failed to get user e, the user is not exit", err)
	//}
	//dbUser, jwt, err := getDBUserAndJwt(ctx, req.ClusterId, user, dbUserSvc, rbacSvc)
	//if err != nil {
	//	return nil, err
	//}
	//
	//response := &acl.GetJwtByUserResp{
	//	Jwt:      *jwt,
	//	DbUser:   *dbUser,
	//	BaseResp: &base.BaseResp{},
	//}
	//
	//return response, nil
}

//func getDBUserAndJwt(ctx context.Context, clusterId uint64, user *entity.User, dbUserSvc dbuser.DBUserIface, rbacSvc rbacSvc.RbacUserSvcIface) (*string, *string, error) {
//	cluster, err := infra.GetHandler().GetClusterById(ctx, clusterId)
//	if err != nil {
//		tilog.Info(ctx, "call cm GetClusterById error", zap.Error(err))
//		return nil, nil, err
//	}
//	if cluster == nil {
//		return nil, nil, errs.NewBadRequest("clusterId is  unavailable", nil)
//	}
//	flag := infra.IsSupportDBUser(ctx, cluster)
//	if !flag {
//		return nil, nil, accountErr.NewErrClusterNotSupportDbUserError("the cluster not support dbuser", nil)
//	}
//	// to check this user  have cluster permissions
//	// 1: org:owner
//	// 2: org:member && （project:owner||project:dev)
//	clusterPermissions := false
//	roles, err := rbacSvc.ListUserRoles(ctx, user.ID.Uint64(), []entity.ScopeType{entity.RoleBindingScopeOrg}, []entity.ID{entity.ID(cluster.TenantId)})
//	if err != nil {
//		tilog.Info(ctx, "rbac ListUserRoles error", zap.Error(err))
//		return nil, nil, err
//	}
//
//	if len(roles) > 0 {
//		userRole := roles[0]
//		if userRole.RoleName == entity.RoleOrgOwner {
//			clusterPermissions = true
//			tilog.Info(ctx, "this user in this org is "+entity.RoleOrgOwner, zap.Any("email", user.Email), zap.Any("org_id", cluster.TenantId))
//		} else if userRole.RoleName == entity.RoleOrgMember {
//			projectRoleFlag, err := rbacSvc.CheckUserRoleInScope(ctx, user.ID.Uint64(), entity.RoleBindingScopeProject, cluster.ProjectId, []string{entity.RoleProjectOwner, entity.RoleProjectDev})
//			if err != nil {
//				tilog.Info(ctx, "CheckUserRoleInScope error", zap.Error(err))
//				return nil, nil, err
//			}
//			if projectRoleFlag {
//				clusterPermissions = true
//			}
//			tilog.Info(ctx, "this user in this org is "+entity.RoleOrgOwner, zap.Any("exist in project", projectRoleFlag), zap.Any("email", user.Email), zap.Any("org_id", cluster.TenantId))
//		}
//	}
//
//	if !clusterPermissions {
//		tilog.Info(ctx, "the user do not have cluster permissions", zap.Any("user_id", user.ID), zap.Any("user_email", user.Email), zap.Any("clusterId", clusterId))
//		return nil, nil, errs.NewBadRequest("the user do not have cluster permissions", nil)
//	}
//	tilog.Info(ctx, "the user  have cluster permissions", zap.Any("user_id", user.ID), zap.Any("user_email", user.Email), zap.Any("clusterId", clusterId))
//	isDedicated := !cluster.IsDevTier
//	getDBUserReq := utils.GetDBUserReq{
//		Email:           user.Email,
//		IsDedicated:     &isDedicated,
//		ServerlessToken: cluster.ServerlessToken,
//	}
//
//	dbUser, err := utils.GetDBUser(ctx, getDBUserReq)
//	if err != nil {
//		return nil, nil, err
//	}
//	jwt, err := dbUserSvc.GetJwtToken(ctx, cluster.TenantId, user.Email, *dbUser, clusterId, isDedicated)
//	if err != nil {
//		return nil, nil, err
//	}
//	return dbUser, &jwt, nil
//}

func (a *accessService) GetOpenAPIKeyByToken(ctx context.Context, req *acl.GetOpenAPIKeyByTokenReq) (*acl.GetOpenAPIKeyByTokenResp, error) {
	if req.PublicKey == "" {
		return nil, errs.NewBadRequest("public_key is required", nil)
	}

	var (
		openAPIKey *entity.OpenAPIKey
	)

	err := database.Transaction(func(tx *gorm.DB) error {
		var e error
		key, e := a.apiKeySvc.GetApiKeyByAccessKey(ctx, req.PublicKey)
		if e != nil {
			return e
		}
		if key == nil {
			return errs.NewBadRequest("token invalid", nil)
		}
		param := apikey2.GetResourceInfoByApiKeyIDParam{
			ApiKeyId: key.Id,
		}
		resourceInfos, e := a.apiKeySvc.ListResourceInfosByApiKeyID(ctx, &param)
		if e != nil {
			return e
		}
		var orgResource *apikey2.ApiKeyResourceInfo
		for _, resourceInfo := range resourceInfos.Items {
			if resourceInfo.ScopeType == entity.RoleBindingScopeOrg.String() {
				orgResource = resourceInfo
				break
			}
		}
		if orgResource == nil {
			return errs.NewBadRequest("api key not grant permission", nil)
		}
		openAPIKey = &entity.OpenAPIKey{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID: entity.ID(key.Id),
				},
			},
			TenantID:     entity.ID(orgResource.ResourceId),
			AccessKey:    key.AccessKey,
			CreatorEmail: key.CreatedBy,
		}

		return e
	})
	if err != nil {
		return nil, errs.NewInternalDB(err.Error(), err)
	}

	resp := &acl.GetOpenAPIKeyByTokenResp{
		Id:           openAPIKey.ID.Uint64(),
		TenantId:     openAPIKey.TenantID.Uint64(),
		PublicKey:    openAPIKey.AccessKey,
		CreatorEmail: openAPIKey.CreatorEmail,
	}

	return resp, nil
}

func (a *accessService) checkAPIKeyDBuser(ctx context.Context, apikey string, clusterId uint64, resp getApiKeyDBUserAndJwtResp) {

	var operatorId uint64 = 0

	ownerIds, err := a.roleBindingSvc.ListUserIdByRoles(ctx, entity.RoleBindingScopeOrg, *resp.orgId, []string{entity.RoleOrgOwner})

	if err != nil {
		tilog.Error(ctx, "ListUserIdByRoles fail", zap.Error(err))
		return
	}

	if len(ownerIds) == 0 {
		tilog.Error(ctx, "this org  owner is nil", zap.Any("orgId", *resp.orgId))
		return
	}
	for _, ownerId := range ownerIds {
		operatorId = ownerId.Uint64()
		break
	}

	owner, err := a.userSvc.GetUserByID(ctx, operatorId)
	if err != nil {
		tilog.Error(ctx, "ListUserIdByRoles fail", zap.Error(err))
		return
	}

	req := utils.GetDBUserReq{
		Email:           owner.Email,
		ClusterId:       &clusterId,
		IsDedicated:     resp.isDedicated,
		ServerlessToken: resp.serverlessToken,
	}

	operatorDBUser, err := utils.GetDBUser(ctx, req)
	if err != nil {
		tilog.Error(ctx, "utils.GetDBUser fail", zap.Error(err))
		return
	}
	jwt, err := a.dbUserSvc.GetJwtToken(ctx, *resp.orgId, owner.Email, *operatorDBUser, clusterId, *resp.isDedicated, 0)
	if err != nil {
		tilog.Error(ctx, "dbUserSvc.GetJwtToken fail", zap.Error(err))
		return
	}

	//tilog.Info(ctx, "check apikey dbuser", zap.Any("apikey", req.AccessKey), zap.Any("clusterId", req.ClusterId), zap.Any("dbUser", dbUser))
	status := infra.GetHandler().CreateDBUser(ctx, clusterId, *operatorDBUser, clusterproxy.AuthMethodToken, jwt, "", *resp.dbUser, true, nil, entity.DbRoleAdmin)

	tilog.Info(ctx, "check  apikey dbuser  result", zap.Any("status", status), zap.Any("ClusterID", clusterId), zap.Any("apikey", apikey))

}
