package dynamodbcheck

import (
	"context"

	"github.com/tidbcloud/account/internel/dao"
	ddbDao "github.com/tidbcloud/account/internel/dao/ddb"
	"github.com/tidbcloud/account/internel/dao/entity"
	rbacDao "github.com/tidbcloud/account/internel/dao/rbac"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// RunPrivilegeRoleCheck checks if rbacRolePrivileges in DynamoDB are consistent with rbacRoles and rbacPrivileges in MySQL
func RunPrivilegeRoleCheck(ctx context.Context, db *gorm.DB, ddb *ddbDao.DynamoDBClient, daoHub *dao.DaoHub) error {
	offset := 0
	for {
		result, err := daoHub.Rbac.TraverseRbacRoles(ctx, db, &rbacDao.ListRoleParam{
			Limit:  BatchSize,
			Offset: offset,
		})
		if err != nil {
			tilog.Error(ctx, "failed to traverse rbacRole table", zap.Error(err))
			return err
		}
		if len(result.List) == 0 {
			break
		}
		offset += len(result.List)

		for _, rbacRole := range result.List {
			ddbPrivilegeRoles, err := ddbDao.GetRbacRolePrivilegesByRoleID(ctx, ddb, rbacRole.ID.Uint64())
			if err != nil {
				tilog.Error(ctx, "failed to get rbacRolePrivileges from DynamoDB", zap.Uint64("ID", rbacRole.ID.Uint64()), zap.Error(err))
				return err
			}
			if rbacRole.Status == entity.Delete {
				if len(ddbPrivilegeRoles) > 0 {
					tilog.Warn(ctx, "rbacRole is deleted but still exists in DynamoDB", zap.Int("ID", int(rbacRole.ID)))
					err := ddbDao.DeleteRolePrivileges(ctx, ddb, ddbPrivilegeRoles)
					if err != nil {
						tilog.Error(ctx, "failed to delete rbacRole from DynamoDB", zap.Uint64("ID", rbacRole.ID.Uint64()), zap.Error(err))
						return err
					}
					tilog.Info(
						ctx, "find extra rbacRolePrivileges (role is deleted) in DynamoDB, delete them",
						zap.Uint64("ID", rbacRole.ID.Uint64()),
					)
				}
			} else if rbacRole.Status == entity.Active {
				privilegeIDs, err := daoHub.Rbac.ListRbacPrivilegesIDsByRoleID(ctx, db, []uint64{rbacRole.ID.Uint64()})
				if err != nil {
					tilog.Error(ctx, "failed to list rbacPrivileges by rbacRoleID", zap.Uint64("ID", rbacRole.ID.Uint64()), zap.Error(err))
					return err
				}
				privileges, err := daoHub.Rbac.ListRbacPrivilegesByIDs(ctx, db, privilegeIDs)
				if err != nil {
					tilog.Error(ctx, "failed to list rbacPrivileges by IDs", zap.Error(err))
					return err
				}
				privilegeRoleChecks := make(map[PrivilegeRoleCheck]int8)
				for _, privilege := range privileges {
					privilegeRoleChecks[PrivilegeRoleCheck{
						ResourceUri: privilege.ResourceUri,
						PrivilegeID: privilege.ID.Uint64(),
					}]++
				}

				for _, ddbPrivilegeRole := range ddbPrivilegeRoles {
					privilegeRoleChecks[PrivilegeRoleCheck{
						ResourceUri: ddbPrivilegeRole.ResourceUri,
						PrivilegeID: ddbPrivilegeRole.PrivilegeID,
					}]--
				}

				for privilegeRoleCheck, count := range privilegeRoleChecks {
					if count == 0 {
						continue
					} else if count > 0 {
						// missing in DynamoDB
						err := ddbDao.CreateRolePrivilege(
							ctx, ddb, privilegeRoleCheck.ResourceUri,
							rbacRole.ID.Uint64(), privilegeRoleCheck.PrivilegeID,
						)
						if err != nil {
							return err
						}
						tilog.Info(
							ctx, "find missing rbacRolePrivilege in DynamoDB, create it",
							zap.Uint64("privilegeID", privilegeRoleCheck.PrivilegeID),
							zap.Uint64("roleID", rbacRole.ID.Uint64()),
						)
					} else {
						// missing in MySQL, delete from DynamoDB
						err := ddbDao.DeleteRolePrivilege(
							ctx, ddb, privilegeRoleCheck.ResourceUri,
							rbacRole.ID.Uint64(),
						)
						if err != nil {
							return err
						}
						tilog.Info(
							ctx, "find extra rbacRolePrivilege in DynamoDB, delete it",
							zap.Uint64("privilegeID", privilegeRoleCheck.PrivilegeID), zap.Uint64("roleID", rbacRole.ID.Uint64()),
						)
					}
				}
			} else {
				tilog.Warn(ctx, "rbacRole has unknown status", zap.Int("ID", int(rbacRole.ID)))
			}
		}
	}
	return nil
}

type PrivilegeRoleCheck struct {
	ResourceUri string
	PrivilegeID uint64
}
