package system

import (
	"context"
	"strings"
	"testing"

	"kra/internal/biz"
	"kra/internal/conf"
)

func newTransactionTestData(t *testing.T) *Data {
	t.Helper()
	name := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
	db, err := openWithDriver("sqlite", "file:"+name+"?mode=memory&cache=shared")
	if err != nil {
		t.Fatal(err)
	}
	if err = migrateAll(db); err != nil {
		t.Fatal(err)
	}
	data := &Data{gormDB: newReloadableDB(db, nil), redis: newReloadableRedis(nil), runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{})}
	t.Cleanup(func() { data.gormDB.close() })
	return data
}

func TestUserAuthorityWritesAreAtomic(t *testing.T) {
	data := newTransactionTestData(t)
	ctx := context.Background()
	if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{{AuthorityID: 888, AuthorityName: "admin"}, {AuthorityID: 999, AuthorityName: "operator"}}).Error; err != nil {
		t.Fatal(err)
	}
	repo := &userRepo{data: data}
	user := &biz.User{Username: "atomic", Password: "hash", NickName: "before", AuthorityID: 888, Enable: 1}
	created, err := repo.CreateUserWithAuthorities(ctx, user, []uint{888, 999})
	if err != nil {
		t.Fatal(err)
	}
	var links int64
	if err = data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_user_id = ?", created.ID).Count(&links).Error; err != nil || links != 2 {
		t.Fatalf("authority links = %d, err = %v", links, err)
	}

	// With strict authority checks disabled, the reference implementation writes the supplied join rows
	// without first requiring every role record to exist.
	created.NickName = "compatible update"
	if err = repo.UpdateUserWithAuthorities(ctx, created, []uint{888, 123456}); err != nil {
		t.Fatalf("missing authority ID was rejected in non-strict mode: %v", err)
	}
	var persisted userPO
	if err = data.gormDB.WithContext(ctx).First(&persisted, created.ID).Error; err != nil {
		t.Fatal(err)
	}
	if persisted.NickName != "compatible update" {
		t.Fatalf("user update was not persisted: %q", persisted.NickName)
	}
	var missingAuthorityLinks int64
	if err = data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).
		Where("sys_user_id = ? AND sys_authority_authority_id = ?", created.ID, 123456).
		Count(&missingAuthorityLinks).Error; err != nil || missingAuthorityLinks != 1 {
		t.Fatalf("missing-authority link count = %d, err = %v", missingAuthorityLinks, err)
	}

	withNewAuthority := &biz.User{Username: "with-new-authority", Password: "hash", AuthorityID: 888, Enable: 1}
	createdWithNewAuthority, err := repo.CreateUserWithAuthorities(ctx, withNewAuthority, []uint{888, 123456})
	if err != nil {
		t.Fatal(err)
	}
	var users int64
	if err = data.gormDB.WithContext(ctx).Model(&userPO{}).Where("username = ?", "with-new-authority").Count(&users).Error; err != nil || users != 1 {
		t.Fatalf("created user count=%d err=%v", users, err)
	}
	var generated authorityPO
	if err = data.gormDB.WithContext(ctx).First(&generated, "authority_id = ?", 123456).Error; err != nil {
		t.Fatal(err)
	}
	if generated.DataScope != 1 || generated.DefaultRouter != "dashboard" {
		t.Fatalf("generated authority defaults = %+v", generated)
	}
	links = 0
	if err = data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_user_id = ?", createdWithNewAuthority.ID).Count(&links).Error; err != nil || links != 2 {
		t.Fatalf("generated authority links = %d, err = %v", links, err)
	}
}

func TestUserLoadMatchesIndependentAuthorityAndDepartmentPreloads(t *testing.T) {
	data := newTransactionTestData(t)
	ctx := context.Background()
	department := departmentPO{Name: "primary-only"}
	if err := data.gormDB.WithContext(ctx).Create(&department).Error; err != nil {
		t.Fatal(err)
	}
	po := userPO{UUID: "00000000-0000-0000-0000-000000000001", Username: "orphaned-role", Password: "hash", AuthorityID: 700001, DeptID: department.ID, Enable: 1}
	if err := data.gormDB.WithContext(ctx).Create(&po).Error; err != nil {
		t.Fatal(err)
	}

	loaded, err := (&userRepo{data: data}).FindUserByID(ctx, po.ID)
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Authority.AuthorityID != 0 {
		t.Fatalf("missing primary authority should preload as zero value: %+v", loaded.Authority)
	}
	if loaded.Department == nil || loaded.Department.ID != department.ID {
		t.Fatalf("primary department was not independently loaded: %+v", loaded.Department)
	}
	if len(loaded.Departments) != 0 {
		t.Fatalf("primary-only department leaked into many-to-many departments: %+v", loaded.Departments)
	}
}

func TestUserRegistrationDoesNotAddPrimaryAuthorityToRequestedAssociations(t *testing.T) {
	data := newTransactionTestData(t)
	ctx := context.Background()
	if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{{AuthorityID: 888, AuthorityName: "primary"}, {AuthorityID: 999, AuthorityName: "requested"}}).Error; err != nil {
		t.Fatal(err)
	}
	created, err := (&userRepo{data: data}).CreateUserWithAuthorities(ctx, &biz.User{Username: "association-shape", Password: "hash", AuthorityID: 888, Enable: 1}, []uint{999})
	if err != nil {
		t.Fatal(err)
	}
	var links []userAuthorityPO
	if err = data.gormDB.WithContext(ctx).Where("sys_user_id = ?", created.ID).Find(&links).Error; err != nil {
		t.Fatal(err)
	}
	if len(links) != 1 || links[0].SysAuthorityAuthorityID != 999 {
		t.Fatalf("registration authority links = %+v", links)
	}
}

func TestDefaultRouterLookupAcceptsAssignedOrphanMenu(t *testing.T) {
	data := newTransactionTestData(t)
	ctx := context.Background()
	menu := menuPO{Name: "orphan-default", ParentID: 999999}
	if err := data.gormDB.WithContext(ctx).Create(&menu).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&authorityMenuPO{SysAuthorityAuthorityID: 888, SysBaseMenuID: menu.ID}).Error; err != nil {
		t.Fatal(err)
	}
	hasMenu, err := (&userRepo{data: data}).HasAuthorityMenu(ctx, 888, "orphan-default")
	if err != nil {
		t.Fatal(err)
	}
	if !hasMenu {
		t.Fatal("assigned default menu was not found")
	}
}

func TestSetUserAuthoritiesRejectsMissingUser(t *testing.T) {
	data := newTransactionTestData(t)
	ctx := context.Background()
	if err := data.gormDB.WithContext(ctx).Create(&authorityPO{AuthorityID: 888, AuthorityName: "admin"}).Error; err != nil {
		t.Fatal(err)
	}
	repo := &userRepo{data: data}
	if err := repo.SetUserAuthorities(ctx, 999999, []uint{888}); err == nil || err.Error() != "查询用户数据失败" {
		t.Fatalf("expected the compatible missing-user error, got %v", err)
	}
	var links int64
	if err := data.gormDB.WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_user_id = ?", 999999).Count(&links).Error; err != nil {
		t.Fatal(err)
	}
	if links != 0 {
		t.Fatalf("unexpected authority links for missing user: %d", links)
	}
}

func TestDictionaryImportKeepsHierarchyInOneTransaction(t *testing.T) {
	data := newTransactionTestData(t)
	ctx := context.Background()
	repo := &dictionaryRepo{data: data}
	parentID := uint(10)
	active := true
	dictionary := &biz.Dictionary{Name: "status", Type: "status", Status: &active}
	details := []*biz.DictionaryDetail{{ID: 10, Label: "parent", Value: "1", Status: &active}, {ID: 11, Label: "child", Value: "2", ParentID: &parentID, Status: &active}}
	if err := repo.ImportDictionary(ctx, dictionary, details); err != nil {
		t.Fatal(err)
	}
	var persisted []dictionaryDetailPO
	if err := data.gormDB.WithContext(ctx).Where("sys_dictionary_id = ?", dictionary.ID).Order("id").Find(&persisted).Error; err != nil {
		t.Fatal(err)
	}
	if len(persisted) != 2 || persisted[1].ParentID == nil || *persisted[1].ParentID != persisted[0].ID {
		t.Fatalf("unexpected imported hierarchy: %+v", persisted)
	}
}
