package rbac

import (
	"context"
	"errors"
	"testing"

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

	"github.com/tidbcloud/account/internel/dao/role"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/pkg/docker_db"
	"gotest.tools/v3/assert"
)

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

	database.DBConn.DB = db

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}
	db.Save(&entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   11,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     1,
	})
	db.Save(&entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   22,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     1,
	})
	ctx := context.TODO()

	relations, err := GetResourceRelationSvc().GetResourceRelations(ctx, entity.RoleBindingScopeTiDB, 22)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 1)
	assert.Assert(t, relations[0].ParentType == entity.RoleBindingScopeOrg.String())
	assert.Assert(t, relations[0].ParentID == 1)

}

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

	database.DBConn.DB = db

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

	ctx := context.Background()
	err = GetResourceRelationSvc().UpsertClusterRelations(ctx, &OperClusterRelationReq{
		ClusterId: 111,
		OrgId:     222,
	})
	assert.Assert(t, err == nil)

	relations, err := GetResourceRelationSvc().GetResourceRelations(ctx, entity.RoleBindingScopeTiDB, 111)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 1)
	for _, ref := range relations {
		if ref.ParentType == entity.RoleBindingScopeOrg.String() {
			assert.Assert(t, ref.ParentID == 222)
		}
	}

	bindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, db, role.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
		ScopeIDs:   []entity.ID{entity.ID(111)},
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{entity.ID(1)},
	})
	assert.Assert(t, err == nil)
	assert.Assert(t, len(bindings) == 0)

}

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

	database.DBConn.DB = db

	err = db.AutoMigrate(&entity.ResourceRelation{})
	if err != nil {
		t.Fatal(err)
	}
	err = db.AutoMigrate(&entity.RoleBinding{})
	if err != nil {
		t.Fatal(err)
	}
	db.Save(&entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   11,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     1,
	})
	db.Save(&entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   11,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     2,
	})
	db.Save(&entity.ResourceRelation{
		ResourceType: entity.RoleBindingScopeTiDB.String(),
		ResourceID:   22,
		ParentType:   entity.RoleBindingScopeOrg.String(),
		ParentID:     1,
	})
	db.Save(&entity.RoleBinding{
		TargetType: entity.RoleBindingTargetUser,
		TargetID:   1,
		ScopeType:  entity.RoleBindingScopeTiDB,
		ScopeID:    11,
	})
	db.Save(&entity.RoleBinding{
		TargetType: entity.RoleBindingTargetAPIKey,
		TargetID:   2,
		ScopeType:  entity.RoleBindingScopeTiDB,
		ScopeID:    11,
	})
	ctx := context.TODO()

	err = GetResourceRelationSvc().DeleteClusterRelations(ctx, 11)
	assert.Assert(t, err == nil)

	relations, err := GetResourceRelationSvc().GetResourceRelations(ctx, entity.RoleBindingScopeTiDB, 11)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 0)

	relations, err = GetResourceRelationSvc().GetResourceRelations(ctx, entity.RoleBindingScopeTiDB, 22)
	assert.Assert(t, err == nil)
	assert.Assert(t, len(relations) == 1)

	bindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, db, role.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
		ScopeIDs:   []entity.ID{entity.ID(11)},
	})
	assert.Assert(t, err == nil)
	assert.Assert(t, len(bindings) == 0)
}

func TestIsDeadlockError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{
			name:     "nil error",
			err:      nil,
			expected: false,
		},
		{
			name:     "normal error",
			err:      errors.New("connection failed"),
			expected: false,
		},
		{
			name:     "error with code 1213",
			err:      errors.New("Error 1213: Deadlock found when trying to get lock"),
			expected: true,
		},
		{
			name:     "error with lowercase deadlock",
			err:      errors.New("database deadlock detected"),
			expected: true,
		},
		{
			name:     "error with uppercase Deadlock",
			err:      errors.New("Deadlock detected in transaction"),
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isDeadlockError(tt.err)
			assert.Assert(t, result == tt.expected)
		})
	}
}
