package rbac

import (
	"context"
	"sync"

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

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

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/account/internel/dao"
	ddbDao "github.com/tidbcloud/account/internel/dao/ddb"
	"github.com/tidbcloud/account/internel/dao/role"
	"gorm.io/gorm"

	"github.com/tidbcloud/account/internel/dao/entity"
)

type RbacRoleBindingIface interface {
	CreateRoleBinding(ctx context.Context, targetType string, targetID uint64, scopeType entity.ScopeType, scopeID uint64, roleID uint64) error
	CreateRoleBindings(ctx context.Context, roleBindings []entity.RoleBinding) error
	DeleteRoleBinding(ctx context.Context, targetType string, targetID uint64, scopeType entity.ScopeType, scopeID uint64, roleID uint64) error
	UpdateRoleOrStatus(ctx context.Context, binding entity.RoleBinding) error
	ListRoleBindings(ctx context.Context, filter role.RoleBindingFilter) ([]entity.RoleBinding, error)
	GetUserOrgIds(ctx context.Context, userId uint64) ([]uint64, error)
	GetUserIdsInOrgs(ctx context.Context, orgIds []uint64) ([]uint64, error)
	ListUserIdByRoles(ctx context.Context, scopeType entity.ScopeType, scopeId uint64, roles []string) ([]entity.ID, error)
}

var (
	rbacRoleBindingInitOnce sync.Once
	rbacRoleBindingSrv      RbacRoleBindingIface
	rbacRoleBindingInitErr  error
)

func GetRbacRoleBindingSvc() (RbacRoleBindingIface, error) {
	rbacRoleBindingInitOnce.Do(func() {
		dynanoDB, err := ddb.NewDynamoDB(config.Global())
		if err != nil {
			rbacRoleBindingInitErr = err
			return
		}
		rbacRoleBindingSrv = NewRbacRoleBindingSvc(database.DBConn.DB, dynanoDB, dao.GetDaoHub())

	})
	return rbacRoleBindingSrv, rbacRoleBindingInitErr
}

type RbacRoleBindingSvc struct {
	db       *gorm.DB
	dynamodb *ddbDao.DynamoDBClient
	hub      *dao.DaoHub
}

func NewRbacRoleBindingSvc(db *gorm.DB, dynamodb *ddbDao.DynamoDBClient, hub *dao.DaoHub) RbacRoleBindingIface {
	return &RbacRoleBindingSvc{
		db:       db,
		dynamodb: dynamodb,
		hub:      hub,
	}
}

func (r *RbacRoleBindingSvc) CreateRoleBinding(ctx context.Context, targetType string, targetID uint64, scopeType entity.ScopeType, scopeID uint64, roleID uint64) error {
	_, err := r.hub.Role.CreateRoleBinding(ctx, r.db, r.dynamodb, entity.TargetType(targetType), targetID, scopeType, scopeID, roleID, entity.RoleBindingStatusActive)
	return err
}
func (r *RbacRoleBindingSvc) CreateRoleBindings(ctx context.Context, roleBindings []entity.RoleBinding) error {
	var reqRoleNames []string

	for _, binding := range roleBindings {
		if !slice.Contain(reqRoleNames, binding.Role) {
			reqRoleNames = append(reqRoleNames, binding.Role)
		}
	}

	//  find  all role by  req.roles
	rbacRoles, err := r.hub.Rbac.GetRbacRoleByNameIn(ctx, r.db, reqRoleNames)
	if err != nil {
		return err
	}
	//  map[role.name]role.id
	roleMap := make(map[string]uint64)
	for _, role := range rbacRoles {
		roleMap[role.Name] = role.ID.Uint64()

	}
	err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		for _, b := range roleBindings {
			_, err := r.hub.Role.CreateRoleBinding(ctx, tx, r.dynamodb, b.TargetType, b.TargetID.Uint64(), b.ScopeType, b.ScopeID.Uint64(), roleMap[b.Role], entity.RoleBindingStatusActive)
			if err != nil {
				tilog.Info(ctx, "CreateRoleBinding err", zap.Any("roleBinding", b), zap.Error(err))
				return err
			}
		}
		return nil
	})
	return err

}
func (r *RbacRoleBindingSvc) DeleteRoleBinding(ctx context.Context, targetType string, targetID uint64, scopeType entity.ScopeType, scopeID uint64, roleID uint64) error {
	_, err := r.hub.Role.DeleteRoleBinding(ctx, r.db, r.dynamodb, entity.TargetType(targetType), targetID, scopeType, scopeID, roleID)
	tilog.Info(ctx, "DeleteRoleBinding", zap.Any("result", err == nil), zap.Any("targetType", targetType), zap.Any("scopeType", scopeType), zap.Any("scopeID", scopeID), zap.Any("roleID", roleID))
	return err

}

func (r *RbacRoleBindingSvc) UpdateRoleOrStatus(ctx context.Context, binding entity.RoleBinding) error {
	// make sure to set role and role_id synchronously
	if binding.Role != "" && binding.RoleID == 0 {
		dbRole, err := r.hub.Rbac.GetRbacRoleByName(ctx, r.db, binding.Role)
		if err != nil {
			return err
		}
		binding.RoleID = dbRole.ID
	} else if binding.RoleID != 0 && binding.Role == "" {
		dbRole, err := r.hub.Rbac.GetRbacRoleByID(ctx, r.db, binding.RoleID.Uint64())
		if err != nil {
			return err
		}
		binding.Role = dbRole.Name
	}
	return r.hub.Role.UpdateRoleOrStatus(ctx, r.db, binding)
}
func (r *RbacRoleBindingSvc) ListRoleBindings(ctx context.Context, filter role.RoleBindingFilter) ([]entity.RoleBinding, error) {
	return r.hub.Role.ListRoleBindings(ctx, r.db, filter)

}
func (r *RbacRoleBindingSvc) GetUserIdsInOrgs(ctx context.Context, orgIds []uint64) ([]uint64, error) {
	return r.hub.Role.GetUserIdsInOrgs(ctx, r.db, orgIds)
}
func (r *RbacRoleBindingSvc) GetUserOrgIds(ctx context.Context, userId uint64) ([]uint64, error) {
	filter := role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{entity.ID(userId)},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		Status:     []string{entity.RoleBindingStatusActive},
	}

	roleBindings, err := r.ListRoleBindings(ctx, filter)

	if err != nil {
		if err == gorm.ErrRecordNotFound {
			tilog.Info(ctx, "user is not belong to any  active org", zap.Any("userID", userId))
		}
		return nil, err
	}
	//orgIds := make([]entity.ID, 0, len(roleBindings))
	var orgIds []uint64

	for _, r := range roleBindings {
		orgIds = append(orgIds, r.ScopeID.Uint64())
	}

	tilog.Info(ctx, "GetUserOrgIds", zap.Any("orgIds", orgIds), zap.Any("userID", userId))
	return orgIds, nil

}

func (r *RbacRoleBindingSvc) ListUserIdByRoles(ctx context.Context, scopeType entity.ScopeType, scopeId uint64, roles []string) ([]entity.ID, error) {
	filter := role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		ScopeTypes: []entity.ScopeType{scopeType},
		ScopeIDs:   []entity.ID{entity.ID(scopeId)},
		Status:     []string{entity.RoleBindingStatusActive},
		Roles:      roles,
	}

	//[]string{entity.RoleOrgOwner},

	roleBindings, err := r.ListRoleBindings(ctx, filter)

	if err != nil {
		if err == gorm.ErrRecordNotFound {
			tilog.Info(ctx, "query user by role  is nil ", zap.Any("roles", roles))
		}
		tilog.Error(ctx, "ListUserIdByRoles fail", zap.Error(err))
		return nil, err
	}
	ids := make([]entity.ID, 0, len(roleBindings))
	if len(roleBindings) > 0 {
		for _, r := range roleBindings {
			ids = append(ids, r.TargetID)
		}

		tilog.Info(ctx, "ListUserIdByRoles result", zap.Any("userIds", ids), zap.Any("roles", roles))
		return ids, nil

	}
	tilog.Info(ctx, "query user by role  is nil ", zap.Any("roles", roles))
	return nil, nil
}
