package system

import (
	"context"
	"strings"
	"testing"

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

func newPolicyTestData(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(&apiPO{}, &casbinRulePO{}, &authorityPO{}, &authorityAPIPO{}); err != nil {
		t.Fatal(err)
	}
	data := &Data{
		gormDB: newReloadableDB(db, nil),
		runtime: conf.NewRuntime(
			&conf.Data{Database: &conf.Data_Database{Driver: "sqlite"}},
			&conf.AdminBackend{System: &conf.AdminBackend_System{UseStrictAuth: true}},
		),
	}
	t.Cleanup(func() { data.gormDB.close() })
	return data
}

func apiPolicyUintPointer(value uint) *uint { return &value }

func TestSetPolicyPathsStrictRootRequiresRegisteredAPI(t *testing.T) {
	data := newPolicyTestData(t)
	ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 888})
	if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{
		{AuthorityID: 888, ParentID: apiPolicyUintPointer(0)},
		{AuthorityID: 999, ParentID: apiPolicyUintPointer(888)},
	}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&apiPO{Path: "/known", Method: "POST"}).Error; err != nil {
		t.Fatal(err)
	}

	err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*biz.API{{Path: "/unknown", Method: "POST"}})
	if err == nil || !strings.Contains(err.Error(), "存在api不在权限列表中") {
		t.Fatalf("unregistered root assignment error = %v", err)
	}
	if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*biz.API{{Path: "/known", Method: "POST"}}); err != nil {
		t.Fatal(err)
	}
}

func TestSetPolicyPathsStrictChildRejectsStalePolicy(t *testing.T) {
	data := newPolicyTestData(t)
	ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 1001})
	if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{
		{AuthorityID: 888, ParentID: apiPolicyUintPointer(0)},
		{AuthorityID: 1001, ParentID: apiPolicyUintPointer(888)},
		{AuthorityID: 1002, ParentID: apiPolicyUintPointer(1001)},
	}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&apiPO{Path: "/known", Method: "POST"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&[]casbinRulePO{
		newPolicyRule(1001, "/known", "POST"),
		newPolicyRule(1001, "/stale", "POST"),
	}).Error; err != nil {
		t.Fatal(err)
	}

	err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 1002, []*biz.API{{Path: "/stale", Method: "POST"}})
	if err == nil || !strings.Contains(err.Error(), "存在api不在权限列表中") {
		t.Fatalf("stale child assignment error = %v", err)
	}
	if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 1002, []*biz.API{{Path: "/known", Method: "POST"}}); err != nil {
		t.Fatal(err)
	}
}

func TestSetPolicyPathsUsesCompatibleDedupeKey(t *testing.T) {
	data := newPolicyTestData(t)
	ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 888})
	if err := data.gormDB.WithContext(ctx).Create(&[]authorityPO{
		{AuthorityID: 888, ParentID: apiPolicyUintPointer(0)},
		{AuthorityID: 999, ParentID: apiPolicyUintPointer(888)},
	}).Error; err != nil {
		t.Fatal(err)
	}
	if err := data.gormDB.WithContext(ctx).Create(&[]apiPO{
		{Path: "/a", Method: "BC"},
		{Path: "/aB", Method: "C"},
	}).Error; err != nil {
		t.Fatal(err)
	}

	if err := (&apiRepo{data: data}).SetPolicyPaths(ctx, 999, []*biz.API{
		{Path: "/a", Method: "BC"},
		{Path: "/aB", Method: "C"},
	}); err != nil {
		t.Fatal(err)
	}
	rows, err := policyRowsForAuthority(data.gormDB.WithContext(ctx), 999)
	if err != nil {
		t.Fatal(err)
	}
	if len(rows) != 1 || rows[0].V1 != "/a" || rows[0].V2 != "BC" {
		t.Fatalf("deduplicated policies = %#v, want only the first concatenated-key match", rows)
	}
}

func TestCheckPolicyStore(t *testing.T) {
	data := newPolicyTestData(t)
	repo := &apiRepo{data: data}
	if err := repo.CheckPolicyStore(context.Background()); err != nil {
		t.Fatalf("CheckPolicyStore returned error for migrated table: %v", err)
	}
	if err := data.gormDB.WithContext(context.Background()).Migrator().DropTable(&casbinRulePO{}); err != nil {
		t.Fatal(err)
	}
	if err := repo.CheckPolicyStore(context.Background()); err == nil {
		t.Fatal("CheckPolicyStore succeeded after casbin_rule was dropped")
	}
}

func TestSetAPIRolesStrictOnlyChangesManagedAuthorities(t *testing.T) {
	data := newPolicyTestData(t)
	db := data.gormDB.WithContext(context.Background())
	rootID, actorID, childID, siblingID := uint(888), uint(2001), uint(2002), uint(3001)
	if err := db.Create(&[]authorityPO{
		{AuthorityID: rootID, ParentID: apiPolicyUintPointer(0)},
		{AuthorityID: actorID, ParentID: &rootID},
		{AuthorityID: childID, ParentID: &actorID},
		{AuthorityID: siblingID, ParentID: &rootID},
	}).Error; err != nil {
		t.Fatal(err)
	}
	if err := db.Create(&apiPO{Path: "/known", Method: "POST"}).Error; err != nil {
		t.Fatal(err)
	}
	if err := db.Create(&[]casbinRulePO{
		newPolicyRule(actorID, "/known", "POST"),
		newPolicyRule(siblingID, "/known", "POST"),
	}).Error; err != nil {
		t.Fatal(err)
	}
	ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
	repo := &apiRepo{data: data}
	if err := repo.SetAPIRoles(ctx, "/known", "POST", []uint{siblingID}); err == nil {
		t.Fatal("SetAPIRoles() accepted an out-of-scope authority")
	}
	if err := repo.SetAPIRoles(ctx, "/known", "POST", []uint{childID}); err != nil {
		t.Fatalf("SetAPIRoles() rejected a managed authority: %v", err)
	}
	if exists, err := policyExists(db, siblingID, "/known", "POST"); err != nil || !exists {
		t.Fatalf("out-of-scope policy exists=%v err=%v, want preserved", exists, err)
	}
	if exists, err := policyExists(db, childID, "/known", "POST"); err != nil || !exists {
		t.Fatalf("managed policy exists=%v err=%v, want added", exists, err)
	}
}
