package resref

import (
	"context"
	"testing"

	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/pkg/docker_db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gotest.tools/v3/assert"
)

var (
	dao = NewResourceRelationDao()
)

func TestResourceRelationDao_CreateResourceRelation(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}

	ctx := context.Background()
	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     2,
	})
	assert.Assert(t, err == nil)

	ref, err := dao.GetResourceRelation(ctx, db, entity.RoleBindingScopeTiDB.String(), 1, entity.RoleBindingScopeOrg.String())
	assert.Assert(t, err == nil)
	assert.Assert(t, ref != nil)
	assert.Assert(t, ref.ParentID == 2)

}

func TestResourceRelationDao_UpsertResourceRelation(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.Exec(`CREATE TABLE IF NOT EXISTS resource_relations  (
		id bigint(50) UNSIGNED NOT NULL AUTO_INCREMENT,
		resource_type varchar(128) NOT NULL DEFAULT '',
		resource_id bigint(50) UNSIGNED NOT NULL DEFAULT 0,
		parent_type varchar(128) NOT NULL DEFAULT '',
		parent_id bigint(50) UNSIGNED NOT NULL DEFAULT 0,
		created_at timestamp NOT NULL,
		updated_at timestamp NOT NULL,
		PRIMARY KEY (id),
		UNIQUE KEY uk_res_pat (resource_type, resource_id, parent_type),
		KEY idx_pat (parent_type, parent_id, resource_type)
		) ENGINE = InnoDB CHARACTER SET = utf8mb4`).Error
	assert.Assert(t, err == nil)

	ctx := context.Background()
	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     2,
	})
	assert.Assert(t, err == nil)

	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     3,
	})
	tilog.Info(ctx, "CreateResourceRelation error", zap.Any("err", err))
	assert.Assert(t, err != nil)

	err = dao.UpsertResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     3,
	})
	assert.Assert(t, err == nil)

	resource, err := dao.GetResourceRelation(ctx, db, entity.RoleBindingScopeTiDB.String(), 1, entity.RoleBindingScopeOrg.String())
	assert.Assert(t, err == nil)
	assert.Assert(t, resource != nil)
	assert.Assert(t, resource.ParentID == 3)

}

func TestResourceRelationDao_GetResourceRelation(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}

	ctx := context.Background()
	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     2,
	})
	assert.Assert(t, err == nil)

	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeProject.String(),
		ParentID:     3,
	})
	assert.Assert(t, err == nil)

	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   2,
		ParentType:   entity.RoleBindingScopeProject.String(),
		ParentID:     3,
	})
	assert.Assert(t, err == nil)

	ref, err := dao.GetResourceRelation(ctx, db, entity.RoleBindingScopeTiDB.String(), 1, entity.RoleBindingScopeOrg.String())
	assert.Assert(t, err == nil)
	assert.Assert(t, ref != nil)
	assert.Assert(t, ref.ParentID == 2)

	resource, err := dao.GetResourceRelationsByResource(ctx, db, entity.RoleBindingScopeTiDB.String(), 1)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(resource) == 2)
	for _, ref := range resource {
		if ref.ParentType == entity.RoleBindingScopeOrg.String() {
			assert.Assert(t, ref.ParentID == 2)
		}
		if ref.ParentType == entity.RoleBindingScopeProject.String() {
			assert.Assert(t, ref.ParentID == 3)
		}
	}

}

func TestResourceRelationDao_GetResourceRelationsByParentAndResourceIDs(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}

	ctx := context.Background()
	for _, relation := range []*entity.ResourceRelation{
		{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   100,
			ParentType:   entity.RoleBindingScopeOrg.String(),
			ParentID:     200,
		},
		{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   101,
			ParentType:   entity.RoleBindingScopeOrg.String(),
			ParentID:     200,
		},
		{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   102,
			ParentType:   entity.RoleBindingScopeOrg.String(),
			ParentID:     201,
		},
		{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   100,
			ParentType:   entity.RoleBindingScopeProject.String(),
			ParentID:     300,
		},
		{
			ResourceType: entity.RoleBindingScopeProject.String(),
			ResourceID:   100,
			ParentType:   entity.RoleBindingScopeOrg.String(),
			ParentID:     200,
		},
	} {
		err = dao.CreateResourceRelation(ctx, db, relation)
		assert.Assert(t, err == nil)
	}

	relations, err := dao.GetResourceIDsByParentAndResourceIDs(ctx, db, entity.RoleBindingScopeOrg.String(), 200, entity.RoleBindingScopeTiDB.String(), []uint64{100, 102, 999})
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 1)
	assert.Assert(t, relations[0] == 100)

}

func TestResourceRelationDao_BatchListResourceRelation(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}

	ctx := context.Background()
	var i uint64 = 1
	for ; i < 10; i++ {
		err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID: entity.ID(i),
				},
			},
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   i,
			ParentType:   entity.RoleBindingScopeOrg.String(),
			ParentID:     2,
		})
		assert.Assert(t, err == nil)
	}

	relations, err := dao.BatchListResourceRelations(ctx, db, 0, 3)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 3)
	assert.Assert(t, relations[0].ResourceID == 1)
	assert.Assert(t, relations[1].ResourceID == 2)
	assert.Assert(t, relations[2].ResourceID == 3)

	relations, err = dao.BatchListResourceRelations(ctx, db, 3, 4)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 4)
	assert.Assert(t, relations[0].ResourceID == 4)
	assert.Assert(t, relations[1].ResourceID == 5)
	assert.Assert(t, relations[2].ResourceID == 6)
	assert.Assert(t, relations[3].ResourceID == 7)

}

func TestResourceRelationDao_DeleteResourceRelationsByResource(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}

	ctx := context.Background()
	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     2,
	})
	assert.Assert(t, err == nil)

	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   1,
		ParentType:   entity.RoleBindingScopeProject.String(),
		ParentID:     3,
	})
	assert.Assert(t, err == nil)

	err = dao.CreateResourceRelation(ctx, db, &entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   2,
		ParentType:   entity.RoleBindingScopeProject.String(),
		ParentID:     3,
	})
	assert.Assert(t, err == nil)

	resource, err := dao.GetResourceRelationsByResource(ctx, db, entity.RoleBindingScopeTiDB.String(), 1)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(resource) == 2)

	resource, err = dao.GetResourceRelationsByResource(ctx, db, entity.RoleBindingScopeTiDB.String(), 2)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(resource) == 1)

	err = dao.DeleteResourceRelationsByResource(ctx, db, entity.RoleBindingScopeTiDB.String(), []uint64{1, 2})
	assert.Assert(t, err == nil)

	resource, err = dao.GetResourceRelationsByResource(ctx, db, entity.RoleBindingScopeTiDB.String(), 1)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(resource) == 0)

	resource, err = dao.GetResourceRelationsByResource(ctx, db, entity.RoleBindingScopeTiDB.String(), 2)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(resource) == 0)

}

func TestResourceRelationDao_BatchUpsertResourceRelations(t *testing.T) {
	id, db, err := docker_db.InitDockerGormDB(false)
	if err != nil {
		t.Fatal(err)
	}
	defer docker_db.ReleaseDockerDB(id)

	err = db.Exec(`CREATE TABLE IF NOT EXISTS resource_relations  (
		id bigint(50) UNSIGNED NOT NULL AUTO_INCREMENT,
		resource_type varchar(128) NOT NULL DEFAULT '',
		resource_id bigint(50) UNSIGNED NOT NULL DEFAULT 0,
		parent_type varchar(128) NOT NULL DEFAULT '',
		parent_id bigint(50) UNSIGNED NOT NULL DEFAULT 0,
		created_at timestamp NOT NULL,
		updated_at timestamp NOT NULL,
		PRIMARY KEY (id),
		UNIQUE KEY uk_res_pat (resource_type, resource_id, parent_type),
		KEY idx_pat (parent_type, parent_id, resource_type)
		) ENGINE = InnoDB CHARACTER SET = utf8mb4`).Error
	assert.Assert(t, err == nil)

	ctx := context.Background()

	relations := []*entity.ResourceRelation{
		{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   100,
			ParentType:   entity.RoleBindingScopeOrg.String(),
			ParentID:     200,
		},
		{
			ResourceType: entity.RoleBindingScopeTiDB.String(),
			ResourceID:   100,
			ParentType:   entity.RoleBindingScopeProject.String(),
			ParentID:     300,
		},
	}

	err = dao.BatchUpsertResourceRelations(ctx, db, relations)
	assert.Assert(t, err == nil)

	org, err := dao.GetResourceRelation(ctx, db, entity.RoleBindingScopeTiDB.String(), 100, entity.RoleBindingScopeOrg.String())
	assert.Assert(t, err == nil)
	assert.Assert(t, org != nil)
	assert.Assert(t, org.ParentID == 200)

	project, err := dao.GetResourceRelation(ctx, db, entity.RoleBindingScopeTiDB.String(), 100, entity.RoleBindingScopeProject.String())
	assert.Assert(t, err == nil)
	assert.Assert(t, project != nil)
	assert.Assert(t, project.ParentID == 300)

	relations[1].ParentID = 400
	err = dao.BatchUpsertResourceRelations(ctx, db, relations)
	assert.Assert(t, err == nil)

	project, err = dao.GetResourceRelation(ctx, db, entity.RoleBindingScopeTiDB.String(), 100, entity.RoleBindingScopeProject.String())
	assert.Assert(t, err == nil)
	assert.Assert(t, project != nil)
	assert.Assert(t, project.ParentID == 400)
}
