package role

import (
	"context"
	"testing"

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

	"gorm.io/gorm"

	"github.com/smartystreets/goconvey/convey"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/pkg/docker_db"
)

var (
	db *gorm.DB
)

func init() {
	var err error
	db, err = docker_db.TryInitCentralDB(true)
	if err != nil {
		panic(err)
	}
}

func newDaoWithContext() (RoleDaoIface, context.Context) {
	ctx := context.TODO()

	return NewDao(), ctx
}

func TestBasicRoleOperations(t *testing.T) {
	targetType, targetID := entity.RoleBindingTargetUser, uint64(42)
	scopeType, scopeID := entity.RoleBindingScopeOrg, uint64(108)
	roleName := "lorem"
	dao, ctx := newDaoWithContext()

	convey.Convey("get non-existed role", t, func() {
		role, err := dao.GetRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName)
		convey.So(err, convey.ShouldNotBeNil)
		convey.So(err, convey.ShouldEqual, gorm.ErrRecordNotFound)
		convey.So(role, convey.ShouldBeNil)

		convey.Convey("create role", func() {
			role, err := dao.CreateRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName, entity.RoleBindingStatusActive)
			convey.So(err, convey.ShouldBeNil)
			convey.So(role, convey.ShouldNotBeNil)
			convey.So(role.ID, convey.ShouldNotBeZeroValue)
			convey.So(role.TargetType, convey.ShouldEqual, targetType)
			convey.So(role.TargetID, convey.ShouldEqual, targetID)
			convey.So(role.ScopeType, convey.ShouldEqual, scopeType)
			convey.So(role.ScopeID, convey.ShouldEqual, scopeID)
			convey.So(role.Role, convey.ShouldEqual, roleName)

			convey.Convey("get role just created", func() {
				newRole, err := dao.GetRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName)
				convey.So(err, convey.ShouldBeNil)
				convey.So(newRole, convey.ShouldNotBeNil)
				convey.So(newRole.ID, convey.ShouldNotBeZeroValue)
				convey.So(newRole.TargetType, convey.ShouldEqual, targetType)
				convey.So(newRole.TargetID, convey.ShouldEqual, targetID)
				convey.So(newRole.ScopeType, convey.ShouldEqual, scopeType)
				convey.So(newRole.ScopeID, convey.ShouldEqual, scopeID)
				convey.So(newRole.Role, convey.ShouldEqual, roleName)
			})

			convey.Convey("create an existed role", func() {
				newRole, err := dao.CreateRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName, entity.RoleBindingStatusActive)
				convey.So(err, convey.ShouldBeNil)
				convey.So(newRole, convey.ShouldBeNil)
			})

			convey.Convey("delete role", func() {
				ok, err := dao.DeleteRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName)
				convey.So(err, convey.ShouldBeNil)
				convey.So(ok, convey.ShouldBeTrue)

				convey.Convey("get deleted role", func() {
					noRole, err := dao.GetRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName)
					convey.So(err, convey.ShouldNotBeNil)
					convey.So(err, convey.ShouldEqual, gorm.ErrRecordNotFound)
					convey.So(noRole, convey.ShouldBeNil)
				})

				convey.Convey("delete non-existed role", func() {
					ok, err := dao.DeleteRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName)
					convey.So(ok, convey.ShouldBeFalse)
					convey.So(err, convey.ShouldNotBeNil)
					convey.So(err, convey.ShouldEqual, gorm.ErrRecordNotFound)
				})
			})

			convey.Reset(func() {
				_, _ = dao.DeleteRole(ctx, db, targetType, targetID, scopeType, scopeID, roleName)
			})
		})
	})
}

func TestListRoles(t *testing.T) {
	roleList := []struct {
		TargetType TargetType
		TargetID   uint64
		ScopeType  ScopeType
		ScopeID    uint64
		Role       string
		Status     string
	}{
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "lorem", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "ipsum", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, "dolor", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, "sit", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, "amet", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeOrg, 108, "consectetur", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeOrg, 108, "adipiscing", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeProject, 112, "elit", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeProject, 112, "sed", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeProject, 115, "do", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeProject, 115, "eiusmod", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeProject, 118, "tempor", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 55, entity.RoleBindingScopeProject, 118, "incididunt", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 59, entity.RoleBindingScopeProject, 118, "ut", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 59, entity.RoleBindingScopeProject, 118, "labore", entity.RoleBindingStatusActive},
		{entity.RoleBindingTargetUser, 59, entity.RoleBindingScopeProject, 118, "lbbore", entity.RoleBindingStatusInactive},
	}

	convey.Convey("list roles by target", t, func() {
		dao, ctx := newDaoWithContext()
		for _, role := range roleList {
			_, err := dao.CreateRole(ctx, db, role.TargetType, role.TargetID, role.ScopeType, role.ScopeID, role.Role, role.Status)
			convey.So(err, convey.ShouldBeNil)
		}

		convey.Convey("list roles by target", func() {
			exp := []string{"lorem", "ipsum", "dolor", "sit", "amet"}

			convey.Convey("list active", func() {
				roles, err := dao.ListRolesByTarget(ctx, db, entity.RoleBindingTargetUser, 42, []string{entity.RoleBindingStatusActive})
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list all", func() {
				roles, err := dao.ListRolesByTarget(ctx, db, entity.RoleBindingTargetUser, 42, []string{})
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list inactive", func() {
				roles, err := dao.ListRolesByTarget(ctx, db, entity.RoleBindingTargetUser, 42, []string{entity.RoleBindingStatusInactive})
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)
				convey.So(len(roles), convey.ShouldEqual, 0)
			})
		})

		convey.Convey("list roles by target on scope list", func() {
			convey.Convey("list all", func() {
				roles, err := dao.ListRolesByTargetOnScopeList(
					ctx, db, entity.RoleBindingTargetUser, 55,
					entity.RoleBindingScopeProject, []uint64{108, 112, 115}, nil,
				)
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"elit", "sed", "do", "eiusmod"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list active", func() {
				roles, err := dao.ListRolesByTargetOnScopeList(
					ctx, db, entity.RoleBindingTargetUser, 55,
					entity.RoleBindingScopeProject, []uint64{108, 112, 115}, []string{entity.RoleBindingStatusActive},
				)
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"elit", "sed", "do", "eiusmod"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list inactive", func() {
				roles, err := dao.ListRolesByTargetOnScopeList(
					ctx, db, entity.RoleBindingTargetUser, 55,
					entity.RoleBindingScopeProject, []uint64{108, 112, 115}, []string{entity.RoleBindingStatusInactive},
				)
				convey.So(err, convey.ShouldBeNil)
				convey.So(len(roles), convey.ShouldEqual, 0)
			})
		})

		convey.Convey("list roles by scope", func() {
			convey.Convey("list all", func() {
				roles, err := dao.ListRolesByScope(ctx, db, entity.RoleBindingScopeOrg, 108, nil)
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"lorem", "ipsum", "consectetur", "adipiscing"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list active", func() {
				roles, err := dao.ListRolesByScope(ctx, db, entity.RoleBindingScopeOrg, 108, []string{entity.RoleBindingStatusActive})
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"lorem", "ipsum", "consectetur", "adipiscing"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list inactive", func() {
				roles, err := dao.ListRolesByScope(ctx, db, entity.RoleBindingScopeOrg, 108, []string{entity.RoleBindingStatusInactive})
				convey.So(err, convey.ShouldBeNil)
				convey.So(len(roles), convey.ShouldEqual, 0)
			})
		})

		convey.Convey("list roles by scope on target list", func() {
			convey.Convey("list all", func() {
				roles, err := dao.ListRolesByScopeOnTargetList(
					ctx, db, entity.RoleBindingScopeProject, 118,
					entity.RoleBindingTargetUser, []uint64{42, 55, 59}, nil,
				)
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"tempor", "incididunt", "ut", "labore", "lbbore"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list active", func() {
				roles, err := dao.ListRolesByScopeOnTargetList(
					ctx, db, entity.RoleBindingScopeProject, 118,
					entity.RoleBindingTargetUser, []uint64{42, 55, 59}, []string{entity.RoleBindingStatusActive},
				)
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"tempor", "incididunt", "ut", "labore"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list inactive", func() {
				roles, err := dao.ListRolesByScopeOnTargetList(
					ctx, db, entity.RoleBindingScopeProject, 118,
					entity.RoleBindingTargetUser, []uint64{42, 55, 59}, []string{entity.RoleBindingStatusInactive},
				)
				convey.So(err, convey.ShouldBeNil)
				convey.So(len(roles), convey.ShouldEqual, 1)

				exp := []string{"lbbore"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})
		})

		convey.Convey("list roles by target and scope", func() {
			convey.Convey("list all", func() {
				roles, err := dao.ListRolesByTargetAndScope(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, nil)
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"dolor", "sit", "amet"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list active", func() {
				roles, err := dao.ListRolesByTargetAndScope(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, []string{entity.RoleBindingStatusActive})
				convey.So(err, convey.ShouldBeNil)
				convey.So(roles, convey.ShouldNotBeNil)

				exp := []string{"dolor", "sit", "amet"}
				got := utils.RoleBindingListToStrings(roles)
				convey.So(got, convey.ShouldResemble, exp)
			})

			convey.Convey("list inactive", func() {
				roles, err := dao.ListRolesByTargetAndScope(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, []string{entity.RoleBindingStatusInactive})
				convey.So(err, convey.ShouldBeNil)
				convey.So(len(roles), convey.ShouldEqual, 0)
			})
		})

		convey.Reset(func() {
			for _, role := range roleList {
				_, err := dao.DeleteRole(ctx, db, role.TargetType, role.TargetID, role.ScopeType, role.ScopeID, role.Role)
				convey.So(err, convey.ShouldBeNil)
			}
		})
	})
}

func TestChangeRoleTo(t *testing.T) {
	roleList := []struct {
		TargetType TargetType
		TargetID   uint64
		ScopeType  ScopeType
		ScopeID    uint64
		Role       string
	}{
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "lorem"},
		{entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 110, "ipsum"},
	}

	convey.Convey("Test change role to", t, func() {
		dao, ctx := newDaoWithContext()
		for _, role := range roleList {
			_, err := dao.CreateRole(ctx, db, role.TargetType, role.TargetID, role.ScopeType, role.ScopeID, role.Role, entity.RoleBindingStatusActive)
			convey.So(err, convey.ShouldBeNil)
		}

		convey.Convey("get role list", func() {
			roles, err := dao.ListRolesByTarget(ctx, db, entity.RoleBindingTargetUser, 42, nil)
			convey.So(err, convey.ShouldBeNil)
			convey.So(roles, convey.ShouldNotBeNil)

			exp := []string{"lorem", "ipsum"}
			got := utils.RoleBindingListToStrings(roles)
			convey.So(got, convey.ShouldResemble, exp)
		})

		convey.Convey("change role", func() {
			newRoleBinding, err := dao.ChangeRoleTo(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "lorem", "dolor")
			convey.So(err, convey.ShouldBeNil)
			//convey.So(newRoleBinding.Role, convey.ShouldNotBeBlank)
			convey.So(newRoleBinding.Role, convey.ShouldEqual, "dolor")

			roles, err := dao.ListRolesByTarget(ctx, db, entity.RoleBindingTargetUser, 42, nil)
			convey.So(err, convey.ShouldBeNil)
			convey.So(roles, convey.ShouldNotBeNil)

			exp := []string{"dolor", "ipsum"}
			got := utils.RoleBindingListToStrings(roles)
			convey.So(got, convey.ShouldResemble, exp)

			convey.Reset(func() {
				_, _ = dao.DeleteRole(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "lorem")
				_, _ = dao.DeleteRole(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "dolor")
			})
		})

		convey.Convey("change not existed role", func() {
			newRoleBinding, err := dao.ChangeRoleTo(ctx, db, entity.RoleBindingTargetUser, 42, entity.RoleBindingScopeOrg, 108, "dolor", "sit")
			convey.So(err, convey.ShouldBeNil)
			convey.So(newRoleBinding.Role, convey.ShouldBeBlank)

			roles, err := dao.ListRolesByTarget(ctx, db, entity.RoleBindingTargetUser, 42, nil)
			convey.So(err, convey.ShouldBeNil)
			convey.So(roles, convey.ShouldNotBeNil)

			exp := []string{"lorem", "ipsum"}
			got := utils.RoleBindingListToStrings(roles)
			convey.So(got, convey.ShouldResemble, exp)
		})

		convey.Reset(func() {
			for _, role := range roleList {
				_, _ = dao.DeleteRole(ctx, db, role.TargetType, role.TargetID, role.ScopeType, role.ScopeID, role.Role)
			}
		})
	})
}

/*func TestRoleDao_CreateRole(t *testing.T) {
	var (
		d                 = NewDao()
		ctx               = context.TODO()
		targetType        = entity.RoleBindingTargetUser
		targetID   uint64 = 1
		scopeType         = entity.RoleBindingScopeOrg
		scopeID    uint64 = 2
		role              = entity.RoleOrgAuditAdmin
	)

	convey.Convey("CreateRole", t, func() {
		roleBinding, err := d.CreateRole(ctx, db, targetType, targetID, scopeType, scopeID, role, entity.RoleBindingStatusInactive)
		convey.So(err, convey.ShouldBeNil)
		convey.So(roleBinding.ID.Uint64(), convey.ShouldBeGreaterThan, uint64(0))

		// create it again
		roleBinding, err = d.CreateRole(ctx, db, targetType, targetID, scopeType, scopeID, role, entity.RoleBindingStatusActive)
		convey.So(err, convey.ShouldBeNil)
		convey.So(roleBinding, convey.ShouldBeNil)
	})
}*/

func TestRoleDao_ListUserRolesByProject(t *testing.T) {
	var (
		d                 = NewDao()
		ctx               = context.TODO()
		targetType        = entity.RoleBindingTargetUser
		targetID   uint64 = 1
		scopeType         = entity.RoleBindingScopeProject
		scopeID    uint64 = 2
		role              = entity.RoleProjectOwner
	)

	convey.Convey("ListUserRolesByProject", t, func() {
		roleBinding, err := d.CreateRole(ctx, db, targetType, targetID, scopeType, scopeID, role, entity.RoleBindingStatusInactive)
		convey.So(err, convey.ShouldBeNil)
		convey.So(roleBinding.ID.Uint64(), convey.ShouldBeGreaterThan, uint64(0))

		bindings, err := d.ListUserRolesByProject(ctx, db, scopeID, nil)
		convey.So(err, convey.ShouldBeNil)
		convey.So(len(bindings), convey.ShouldEqual, 1)

		bindings, err = d.ListUserRolesByProject(ctx, db, scopeID+1, nil)
		convey.So(err, convey.ShouldBeNil)
		convey.So(len(bindings), convey.ShouldEqual, 0)
	})
}
