package system

import (
	"context"
	"kra/internal/biz/system"
	"testing"
	"time"

	"kra/internal/conf"
)

func newMenuTestData(t *testing.T) *Data {
	t.Helper()
	db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
	if err != nil {
		t.Fatal(err)
	}
	if err = db.AutoMigrate(&menuPO{}, &menuParameterPO{}, &menuButtonPO{}, &authorityButtonPO{}, &authorityPO{}, &authorityMenuPO{}); err != nil {
		t.Fatal(err)
	}
	data := &Data{
		gormDB:  newReloadableDB(db, nil),
		runtime: conf.NewRuntime(&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}}, &conf.AdminBackend{}),
	}
	t.Cleanup(func() { data.gormDB.close() })
	return data
}

func TestMenuCreatePreservesSubmittedTimestamps(t *testing.T) {
	data := newMenuTestData(t)
	ctx := context.Background()
	createdAt := time.Date(2025, time.January, 2, 3, 4, 5, 0, time.UTC)
	updatedAt := time.Date(2025, time.February, 3, 4, 5, 6, 0, time.UTC)
	menu := &system.Menu{CreatedAt: createdAt, UpdatedAt: updatedAt, Name: "created", Path: "created", Component: "view/created.vue", Title: "Created"}

	if err := (&menuRepo{data: data}).Create(ctx, menu); err != nil {
		t.Fatal(err)
	}
	var stored menuPO
	if err := data.gormDB.WithContext(ctx).First(&stored, menu.ID).Error; err != nil {
		t.Fatal(err)
	}
	if !stored.CreatedAt.Equal(createdAt) || !stored.UpdatedAt.Equal(updatedAt) {
		t.Fatalf("stored timestamps = (%v, %v), want (%v, %v)", stored.CreatedAt, stored.UpdatedAt, createdAt, updatedAt)
	}
}

func TestSetAuthorityMenusRejectsMissingAuthority(t *testing.T) {
	data := newMenuTestData(t)
	err := (&menuRepo{data: data}).SetAuthorityMenus(context.Background(), 999, nil)
	if err == nil {
		t.Fatal("SetAuthorityMenus missing authority succeeded")
	}
}

func TestSetMenuRolesPreservesDuplicateAuthorityIDs(t *testing.T) {
	data := newMenuTestData(t)
	ctx := context.Background()
	if err := (&menuRepo{data: data}).SetMenuRoles(ctx, 10, []uint{888, 888}); err != nil {
		t.Fatal(err)
	}
	var count int64
	if err := data.gormDB.WithContext(ctx).Model(&authorityMenuPO{}).
		Where("sys_base_menu_id = ? AND sys_authority_authority_id = ?", 10, 888).
		Count(&count).Error; err != nil {
		t.Fatal(err)
	}
	if count != 2 {
		t.Fatalf("authority-menu rows = %d, want duplicate role IDs preserved", count)
	}
}

func TestMenuUpdateRebuildsRelationsWithoutChangingRequestedIdentity(t *testing.T) {
	data := newMenuTestData(t)
	ctx := context.Background()
	createdAt := time.Date(2025, time.March, 4, 5, 6, 7, 0, time.UTC)
	updatedAt := time.Date(2025, time.April, 5, 6, 7, 8, 0, time.UTC)

	if err := data.gormDB.WithContext(ctx).Create(&menuPO{ID: 10, Name: "before", Path: "before", Component: "view/before.vue", Title: "Before"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&menuParameterPO{ID: 21, CreatedAt: createdAt, UpdatedAt: updatedAt, MenuID: 10, Type: "query", Key: "old", Value: "old"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&menuButtonPO{ID: 31, CreatedAt: createdAt, UpdatedAt: updatedAt, MenuID: 10, Name: "edit", Description: "old"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&authorityButtonPO{AuthorityID: 888, MenuID: 10, ButtonID: 31}).Error; err != nil {
		t.Fatal(err)
	}

	repo := &menuRepo{data: data}
	err := repo.Update(ctx, &system.Menu{
		ID:        10,
		Name:      "after",
		Path:      "after",
		Component: "view/after.vue",
		Title:     "After",
		Parameters: []*system.MenuParameter{{
			ID: 21, CreatedAt: createdAt, UpdatedAt: updatedAt, Type: "params", Key: "new", Value: "value",
		}},
		Buttons: []*system.MenuButton{{
			ID: 31, CreatedAt: createdAt, UpdatedAt: updatedAt, Name: "edit", Description: "new",
		}},
	})
	if err != nil {
		t.Fatal(err)
	}

	var parameter menuParameterPO
	if err = data.gormDB.WithContext(ctx).First(&parameter, 21).Error; err != nil {
		t.Fatal(err)
	}
	if parameter.MenuID != 10 || parameter.Type != "params" || parameter.Key != "new" || parameter.Value != "value" {
		t.Fatalf("parameter = %+v", parameter)
	}
	if !parameter.CreatedAt.Equal(createdAt) || !parameter.UpdatedAt.Equal(updatedAt) {
		t.Fatalf("parameter timestamps = (%v, %v), want (%v, %v)", parameter.CreatedAt, parameter.UpdatedAt, createdAt, updatedAt)
	}

	var button menuButtonPO
	if err = data.gormDB.WithContext(ctx).First(&button, 31).Error; err != nil {
		t.Fatal(err)
	}
	if button.MenuID != 10 || button.Name != "edit" || button.Description != "new" {
		t.Fatalf("button = %+v", button)
	}
	if !button.CreatedAt.Equal(createdAt) || !button.UpdatedAt.Equal(updatedAt) {
		t.Fatalf("button timestamps = (%v, %v), want (%v, %v)", button.CreatedAt, button.UpdatedAt, createdAt, updatedAt)
	}

	var links int64
	if err = data.gormDB.WithContext(ctx).Model(&authorityButtonPO{}).
		Where("authority_id = ? AND sys_menu_id = ? AND sys_base_menu_btn_id = ?", 888, 10, 31).
		Count(&links).Error; err != nil {
		t.Fatal(err)
	}
	if links != 1 {
		t.Fatalf("authority-button links = %d, want 1", links)
	}
}

func TestSetMenuRolesStrictOnlyChangesManagedAuthorities(t *testing.T) {
	data := newMenuTestData(t)
	enableStrictAuthorityTestMode(data)
	db := data.gormDB.WithContext(context.Background())
	rootID, actorID, childID, siblingID := uint(888), uint(4001), uint(4002), uint(5001)
	if err := db.Create(&[]authorityPO{
		{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
		{AuthorityID: actorID, ParentID: &rootID},
		{AuthorityID: childID, ParentID: &actorID},
		{AuthorityID: siblingID, ParentID: &rootID},
	}).Error; err != nil {
		t.Fatal(err)
	}
	menu := menuPO{ID: 10, Name: "managed", Path: "managed"}
	if err := db.Create(&menu).Error; err != nil {
		t.Fatal(err)
	}
	if err := db.Create(&[]authorityMenuPO{
		{SysAuthorityAuthorityID: actorID, SysBaseMenuID: menu.ID},
		{SysAuthorityAuthorityID: siblingID, SysBaseMenuID: menu.ID},
	}).Error; err != nil {
		t.Fatal(err)
	}
	ctx := system.NewActorContext(context.Background(), system.Actor{AuthorityID: actorID})
	repo := &menuRepo{data: data}
	if err := repo.SetMenuRoles(ctx, menu.ID, []uint{siblingID}); err == nil {
		t.Fatal("SetMenuRoles() accepted an out-of-scope authority")
	}
	if err := repo.SetMenuRoles(ctx, menu.ID, []uint{childID}); err != nil {
		t.Fatalf("SetMenuRoles() rejected a managed authority: %v", err)
	}
	var siblingCount, childCount int64
	if err := db.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", siblingID, menu.ID).Count(&siblingCount).Error; err != nil {
		t.Fatal(err)
	}
	if err := db.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", childID, menu.ID).Count(&childCount).Error; err != nil {
		t.Fatal(err)
	}
	if siblingCount != 1 || childCount != 1 {
		t.Fatalf("menu-role counts sibling/child = %d/%d, want 1/1", siblingCount, childCount)
	}
}
