package store

import (
	"context"
	"testing"

	"github.com/glebarez/sqlite" // 纯 Go sqlite（无 CGO）：DB 背书的单测在 CI ubuntu 无 Postgres 服务时也能跑
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// newTestStore 起一个内存 sqlite，迁移同款模型 + 建那道支付幂等兜底的部分唯一索引 +
// 挂租户作用域回调，尽量贴近生产 Postgres 的行为（核心事务/CAS/OnConflict 在两者一致）。
// 返回的 *Postgres 直接复用生产的 store 方法——测的是真逻辑，不是替身。
func newTestStore(t *testing.T) *Postgres {
	t.Helper()
	// 静音 gorm 日志：计费路径故意查 pricing/setting 取不到时回退默认，属预期空查询，别刷屏。
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		t.Fatalf("打开内存 sqlite 失败: %v", err)
	}
	sqlDB, err := db.DB()
	if err != nil {
		t.Fatalf("取 *sql.DB 失败: %v", err)
	}
	sqlDB.SetMaxOpenConns(1) // :memory: 每连接一个库，锁死单连接才共享同一份数据

	if err := db.AutoMigrate(
		&User{}, &Tenant{}, &TenantMember{}, &CreditLedger{}, &PaymentOrder{},
		&RedeemCode{}, &CreditPack{}, &UsageEvent{}, &UsageRollup{}, &Setting{}, &Pricing{}, &LLMModel{},
		&KB{}, // 租户作用域模型，验证隔离插件
	); err != nil {
		t.Fatalf("AutoMigrate 失败: %v", err)
	}
	// 支付入账幂等兜底闸：与 pgsql.go 生产建的同一道部分唯一索引（sqlite 同样支持）。
	if err := db.Exec(`CREATE UNIQUE INDEX idx_ledger_grant_ref ON sundynix_credit_ledger (kind, ref) WHERE kind = 'grant' AND ref <> ''`).Error; err != nil {
		t.Fatalf("建幂等索引失败: %v", err)
	}
	registerTenantScope(db)
	return &Postgres{db: db}
}

// seedTenant 建一个租户行（GrantCredits/MarkOrderPaid 靠 UpdateColumn 更新它的物化余额，
// 无租户行则余额更新落空 → 破坏「余额 == SUM(ledger)」不变量，故测试必须先建）。
func seedTenant(t *testing.T, p *Postgres, id string) {
	t.Helper()
	if err := p.db.Create(&Tenant{BaseModel: BaseModel{ID: id}, Name: "T-" + id, Slug: "slug-" + id, Status: "active"}).Error; err != nil {
		t.Fatalf("建租户失败: %v", err)
	}
}

// balanceEqualsSumLedger 是计费系统的核心不变量：物化余额 == 账本分录之和。
// 任何入账/扣费路径违反它都是对账事故。
func assertBalanceInvariant(t *testing.T, p *Postgres, tenantID string) {
	t.Helper()
	ctx := WithoutTenant(context.Background())
	var sum int64
	p.db.WithContext(ctx).Model(&CreditLedger{}).Where("tenant_id = ?", tenantID).
		Select("coalesce(sum(credits_micro),0)").Scan(&sum)
	bal := p.TenantBalance(ctx, tenantID)
	if bal != sum {
		t.Fatalf("不变量破坏：物化余额=%d 但 SUM(ledger)=%d", bal, sum)
	}
}
