package store

import (
	"context"
	"testing"
)

// 租户数据层隔离（P0-3）：tenant_scope 插件是多租户的命根，此前只测了角色门禁、
// 没测数据层是否真隔离。一个回归就可能串租户数据——SaaS 里这是致命的。
// 用租户作用域模型 KB 验证：创建自动填 tenant_id + 查询自动按 ctx 租户过滤 + WithoutTenant 跨租户可见。

func countKB(t *testing.T, p *Postgres, ctx context.Context) int64 {
	t.Helper()
	var n int64
	if err := p.db.WithContext(ctx).Model(&KB{}).Count(&n).Error; err != nil {
		t.Fatalf("计数失败: %v", err)
	}
	return n
}

func TestTenantScope_CreateAutoFillAndQueryFilter(t *testing.T) {
	p := newTestStore(t)
	ctxA := WithTenant(context.Background(), "tenant-A")
	ctxB := WithTenant(context.Background(), "tenant-B")

	// 在 A 的上下文里建库，不显式写 tenant_id —— 插件应自动填成 tenant-A。
	kb := &KB{Name: "A的库", Owner: "u1", Kind: "general"}
	if err := p.db.WithContext(ctxA).Create(kb).Error; err != nil {
		t.Fatalf("建库失败: %v", err)
	}
	var got KB
	p.db.WithContext(WithoutTenant(context.Background())).First(&got, "id = ?", kb.ID)
	if got.TenantID != "tenant-A" {
		t.Fatalf("创建应自动填 tenant_id=tenant-A，实得 %q", got.TenantID)
	}

	// B 的上下文查不到 A 的库（隔离）。
	if n := countKB(t, p, ctxB); n != 0 {
		t.Fatalf("tenant-B 不该看到 tenant-A 的库，却查到 %d 条", n)
	}
	// A 的上下文能查到自己的。
	if n := countKB(t, p, ctxA); n != 1 {
		t.Fatalf("tenant-A 应看到自己 1 条库，实得 %d", n)
	}
}

func TestTenantScope_CrossTenantLeakGuard(t *testing.T) {
	p := newTestStore(t)
	ctxA := WithTenant(context.Background(), "tenant-A")
	ctxB := WithTenant(context.Background(), "tenant-B")

	p.db.WithContext(ctxA).Create(&KB{Name: "A1", Kind: "general"})
	p.db.WithContext(ctxA).Create(&KB{Name: "A2", Kind: "general"})
	p.db.WithContext(ctxB).Create(&KB{Name: "B1", Kind: "general"})

	if n := countKB(t, p, ctxA); n != 2 {
		t.Fatalf("A 应见 2 条，实得 %d", n)
	}
	if n := countKB(t, p, ctxB); n != 1 {
		t.Fatalf("B 应见 1 条，实得 %d", n)
	}
	// WithoutTenant（系统/admin 聚合口径）应看到全部 3 条。
	if n := countKB(t, p, WithoutTenant(context.Background())); n != 3 {
		t.Fatalf("WithoutTenant 应见全部 3 条，实得 %d", n)
	}
	// 无 tenant 上下文（既非 WithTenant 也非 WithoutTenant）：插件不过滤，等同系统视角
	// —— 这是设计约定（回填/未登录路径），用户面由中间件保证必有 tenant。
	if n := countKB(t, p, context.Background()); n != 3 {
		t.Fatalf("裸 ctx 不过滤应见全部 3 条，实得 %d", n)
	}
}

func TestTenantScope_UpdateAndDeleteScoped(t *testing.T) {
	p := newTestStore(t)
	ctxA := WithTenant(context.Background(), "tenant-A")
	ctxB := WithTenant(context.Background(), "tenant-B")

	a := &KB{Name: "A的库", Kind: "general"}
	p.db.WithContext(ctxA).Create(a)

	// B 的上下文尝试改 A 的库 —— 插件按 tenant-B 过滤，命不中，改不动（防越权写他租）。
	res := p.db.WithContext(ctxB).Model(&KB{}).Where("id = ?", a.ID).Update("name", "被B改了")
	if res.RowsAffected != 0 {
		t.Fatalf("B 不该能改 A 的库，却影响了 %d 行", res.RowsAffected)
	}
	// B 的上下文删 A 的库 —— 同样命不中。
	res = p.db.WithContext(ctxB).Where("id = ?", a.ID).Delete(&KB{})
	if res.RowsAffected != 0 {
		t.Fatalf("B 不该能删 A 的库，却删了 %d 行", res.RowsAffected)
	}
	// A 自己能改。
	res = p.db.WithContext(ctxA).Model(&KB{}).Where("id = ?", a.ID).Update("name", "A自己改")
	if res.RowsAffected != 1 {
		t.Fatalf("A 应能改自己的库，实影响 %d 行", res.RowsAffected)
	}
}
