package rbac_test

import (
	"context"
	"sort"
	"testing"

	"github.com/onsi/gomega"

	"github.com/pingcap-inc/clinic/pkg/db/model"
	. "github.com/pingcap-inc/clinic/pkg/rbac"
)

func Test_Auth(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	//res, err := auth.Auth(444, true, Cluster(), ReadActing(), 1)
	//g.Expect(err).To(gomega.Succeed())
	//g.Expect(res).To(gomega.BeTrue())
	//
	res, err := auth.Auth(444, false, Cluster(), ReadActing(), 1)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(res).To(gomega.BeFalse())

	orgID := uint(110)
	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	role, err := model.GetRoleByName(Admin, orgID)
	g.Expect(err).To(gomega.Succeed())

	err = auth.AttachRoleForUser(orgID, 11, role.ID)
	g.Expect(err).To(gomega.Succeed())

	res, err = auth.Auth(11, false, User(), WriteActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(res).To(gomega.BeTrue())

	res, err = auth.Auth(12, false, User(), WriteActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(res).To(gomega.BeFalse())

	res, err = auth.Auth(13, true, Cluster(), WriteActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(res).To(gomega.BeFalse())
}

func Test_GetUserAccessOrgs(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	orgID := uint(111)
	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	role, err := model.GetRoleByName(Admin, orgID)
	g.Expect(err).To(gomega.Succeed())

	userID := uint(111)
	err = auth.AttachRoleForUser(orgID, userID, role.ID)
	g.Expect(err).To(gomega.Succeed())

	orgs := auth.GetUserAccessOrgs(userID)
	g.Expect(len(orgs)).To(gomega.Equal(1))
	g.Expect(orgs[0]).To(gomega.Equal(orgID))

	for i := 1; i <= 1000; i++ {
		err = auth.AttachRoleForUser(uint(i), userID, uint(i))
		g.Expect(err).To(gomega.Succeed())
	}

	for i := 1; i < 399; i++ {
		err = auth.AttachRoleForUser(uint(i), userID, uint(i))
		g.Expect(err).To(gomega.Succeed())
	}

	orgs = auth.GetUserAccessOrgs(userID)
	g.Expect(len(orgs)).To(gomega.Equal(1000))

	// sort.
	sort.Slice(orgs, func(i, j int) bool {
		return orgs[i] < orgs[j]
	})

	for i := 1; i <= 1000; i++ {
		g.Expect(orgs[i-1]).To(gomega.Equal(uint(i)))
	}
}

func Test_GetRolesOfUserInOrg(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	orgID := uint(112)
	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	role, err := model.GetRoleByName(Admin, orgID)
	g.Expect(err).To(gomega.Succeed())

	userID := uint(112)
	err = auth.AttachRoleForUser(orgID, userID, role.ID)
	g.Expect(err).To(gomega.Succeed())

	orgs := auth.GetUserAccessOrgs(userID)
	g.Expect(len(orgs)).To(gomega.Equal(1))
	g.Expect(orgs[0]).To(gomega.Equal(orgID))

	for i := 1; i <= 200; i++ {
		err = auth.AttachRoleForUser(uint(i), userID, uint(i))
		g.Expect(err).To(gomega.Succeed())
	}

	for i := 1; i < 139; i++ {
		err = auth.AttachRoleForUser(uint(i), userID, uint(i))
		g.Expect(err).To(gomega.Succeed())
	}

	editorRole, err := model.GetRoleByName("editor", orgID)
	g.Expect(err).To(gomega.Succeed())

	viewRole, err := model.GetRoleByName("viewer", orgID)
	g.Expect(err).To(gomega.Succeed())

	roles := auth.GetRolesOfUserInOrg(userID, orgID)
	g.Expect(len(roles)).To(gomega.Equal(4))

	sort.Slice(roles, func(i, j int) bool {
		return roles[i] < roles[j]
	})

	g.Expect(roles).To(gomega.ContainElement(uint(112)))
	g.Expect(roles).To(gomega.ContainElement(role.ID))
	g.Expect(roles).To(gomega.ContainElement(editorRole.ID))
	g.Expect(roles).To(gomega.ContainElement(viewRole.ID))
}

func Test_GetAllUsersInOrg(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	orgID := uint(1113)
	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	for i := 1; i <= 100; i++ {
		err = auth.AttachRoleForUser(orgID, uint(i), uint(i))
		g.Expect(err).To(gomega.Succeed())
	}

	users := auth.GetAllUsersInOrg(orgID)
	g.Expect(len(users)).To(gomega.Equal(100))

	sort.Slice(users, func(i, j int) bool {
		return users[i] < users[j]
	})
	for i := 0; i < 100; i++ {
		g.Expect(users[i]).To(gomega.Equal(uint(i + 1)))
	}
}

func Test_GetOneUserAccessOrg(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	userID := uint(1114)

	oids := make([]uint, 0)
	for i := 1; i <= 200; i++ {
		err = auth.AttachRoleForUser(uint(i), userID, uint(i))
		g.Expect(err).To(gomega.Succeed())
		oids = append(oids, uint(i))
	}

	g.Expect(oids).To(gomega.ContainElement(auth.GetOneUserAccessOrg(userID)))

	g.Expect(auth.GetOneUserAccessOrg(11112)).To(gomega.Equal(uint(0)))
}

func Test_DeleteRoleForUser(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	userID := uint(1115)
	orgID := uint(1115)

	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	role, err := model.GetRoleByName(Admin, orgID)
	g.Expect(err).To(gomega.Succeed())

	err = auth.AttachRoleForUser(orgID, userID, role.ID)
	g.Expect(err).To(gomega.Succeed())

	ok, err := auth.Auth(userID, false, User(), WriteActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())

	err = auth.DeleteRoleForUser(orgID, userID, role.ID)
	g.Expect(err).To(gomega.Succeed())

	ok, err = auth.Auth(userID, false, User(), WriteActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeFalse())
}

func Test_InternalWritePermission(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	userID := uint(111508)
	orgID := uint(111589)

	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	ok, err := auth.Auth(userID, true, Cluster(), WriteActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeFalse())
}

func Test_InternalReadPermission(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())
	// ensure role pingcaper is already inserted
	_, err = auth.GetEnforcer().AddPolicy("role/pingcaper", "*", "cluster", "read")
	g.Expect(err).To(gomega.Succeed())
	_, err = auth.GetEnforcer().AddPolicy("role/pingcaper", "*", "cluster", "analyze")
	g.Expect(err).To(gomega.Succeed())

	userID := uint(111523)
	orgID := uint(1115532)

	_, err = auth.GetEnforcer().AddGroupingPolicy("user/111523", "role/pingcaper", "*")
	g.Expect(err).To(gomega.Succeed())

	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	ok, err := auth.Auth(userID, true, Cluster(), ReadActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())
}

func Test_RebuildPermission(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	// ensure role pingcaper is already inserted
	_, err = auth.GetEnforcer().AddPolicy("role/pingcaper", "*", "cluster", "read")
	g.Expect(err).To(gomega.Succeed())
	_, err = auth.GetEnforcer().AddPolicy("role/pingcaper", "*", "cluster", "analyze")
	g.Expect(err).To(gomega.Succeed())

	userID := uint(111512)
	orgID := uint(111512)

	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	role, err := model.GetRoleByName(Admin, orgID)
	g.Expect(err).To(gomega.Succeed())

	err = auth.AttachRoleForUser(orgID, userID, role.ID)
	g.Expect(err).To(gomega.Succeed())

	// admin role
	ok, err := auth.Auth(userID, false, Cluster(), RebuildActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())

	// internal role
	_, err = auth.GetEnforcer().AddGroupingPolicy("user/111111259", "role/pingcaper", "*")
	g.Expect(err).To(gomega.Succeed())
	ok, err = auth.Auth(111111259, true, Cluster(), RebuildActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())

	role, err = model.GetRoleByName("editor", orgID)
	g.Expect(err).To(gomega.Succeed())
	err = auth.AttachRoleForUser(orgID, 1189, role.ID)
	g.Expect(err).To(gomega.Succeed())

	// editor role
	ok, err = auth.Auth(1189, false, Cluster(), RebuildActing(), orgID)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())
}

func Test_Enforce(t *testing.T) {
	g := gomega.NewGomegaWithT(t)
	auth, err := InitAuth(cfg.DBConfig, "../../rbac_model")
	g.Expect(err).To(gomega.Succeed())

	userID := uint(11151212)
	orgID := uint(11151212)

	err = auth.InitOrgRBAC(orgID)
	g.Expect(err).To(gomega.Succeed())

	role, err := model.GetRoleByName(Admin, orgID)
	g.Expect(err).To(gomega.Succeed())

	err = auth.AttachRoleForUser(orgID, userID, role.ID)
	g.Expect(err).To(gomega.Succeed())

	// admin role
	identity := &EnforceRequest{
		User: &Identity{
			Id:       uint32(userID),
			Internal: true,
		},
		Uri: "orgs/123/test/1",
	}

	result, err := auth.Enforce(context.Background(), identity)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(result == nil).To(gomega.BeFalse())

	identity.Uri = "orgs/11151212/cluster/read"
	result, err = auth.Enforce(context.Background(), identity)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(result.Res).To(gomega.BeTrue())

	identity = &EnforceRequest{
		User: &Identity{
			Id:       uint32(112222333),
			Internal: true,
		},
		Uri: "orgs/11151212/cluster/read",
	}

	result, err = auth.Enforce(context.Background(), identity)
	g.Expect(err).To(gomega.Succeed())
	g.Expect(result.Res).To(gomega.BeFalse())
}
