package quota

import (
	"context"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2"
	"github.com/glebarez/sqlite"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"

	"dudu/server/internal/store"
	"dudu/server/pkg/protocol"
)

func setup(t *testing.T) (*Manager, *redis.Client, *gorm.DB) {
	t.Helper()
	mr := miniredis.RunT(t)
	rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AutoMigrate(store.AllModels()...); err != nil {
		t.Fatal(err)
	}
	return New(rdb, db), rdb, db
}

func TestConsumeTrialFirstThenBalance(t *testing.T) {
	m, _, db := setup(t)
	ctx := context.Background()
	db.Create(&store.User{ID: "u1", BalanceSeconds: 100})

	// 第一次扣 60s：全部走试用（180s/天）
	r, err := m.Consume(ctx, "u1", 60)
	if err != nil {
		t.Fatal(err)
	}
	if r.TrialPart != 60 || r.BalancePart != 0 {
		t.Fatalf("want trial=60 balance=0, got %+v", r)
	}

	// 再扣 150s：试用只剩 120s，余下 30s 走余额
	r, _ = m.Consume(ctx, "u1", 150)
	if r.TrialPart != 120 || r.BalancePart != 30 || r.BalanceSeconds != 70 {
		t.Fatalf("want trial=120 balance=30 remain=70, got %+v", r)
	}

	// 再扣 100s：试用已尽，余额只剩 70 → 扣穿到 0（不掐断当句，由网关句间判断）
	r, _ = m.Consume(ctx, "u1", 100)
	if r.TrialPart != 0 || r.BalancePart != 70 || r.BalanceSeconds != 0 {
		t.Fatalf("want trial=0 balance=70 remain=0, got %+v", r)
	}

	s, _ := m.Get(ctx, "u1")
	if !s.Exhausted() {
		t.Fatalf("expect exhausted, got %+v", s)
	}
}

func TestPrecheck(t *testing.T) {
	m, _, db := setup(t)
	ctx := context.Background()
	db.Create(&store.User{ID: "u2", BalanceSeconds: 0})

	// 余额 0 但有当日试用 → 放行
	ok, _, err := m.Precheck(ctx, "u2")
	if err != nil || !ok {
		t.Fatalf("expect precheck pass (trial available), ok=%v err=%v", ok, err)
	}
	// 用尽试用 → 拒绝
	if _, err := m.Consume(ctx, "u2", protocol.TrialDailySeconds); err != nil {
		t.Fatal(err)
	}
	ok, _, _ = m.Precheck(ctx, "u2")
	if ok {
		t.Fatal("expect precheck reject after trial exhausted")
	}
}

func TestCreditAndSettle(t *testing.T) {
	m, rdb, db := setup(t)
	ctx := context.Background()
	db.Create(&store.User{ID: "u3", BalanceSeconds: 0})

	if err := m.CreditPurchase(ctx, "u3", 6000, "o1"); err != nil {
		t.Fatal(err)
	}
	var u store.User
	db.First(&u, "id = ?", "u3")
	if u.BalanceSeconds != 6000 {
		t.Fatalf("want 6000, got %d", u.BalanceSeconds)
	}
	var n int64
	db.Model(&store.BalanceLedger{}).Where("user_id = ? AND reason = ?", "u3", store.LedgerPurchase).Count(&n)
	if n != 1 {
		t.Fatalf("want 1 ledger row, got %d", n)
	}

	// 落库会话：30s = 试用 20 + 余额 10
	err := m.SettleSession(ctx, store.ASRSession{
		ID: "s1", UserID: "u3", AudioSeconds: 30, TrialPart: 20, BalancePart: 10,
		Provider: "mock", CreatedAt: time.Now(),
	})
	if err != nil {
		t.Fatal(err)
	}
	db.First(&u, "id = ?", "u3")
	if u.BalanceSeconds != 5990 {
		t.Fatalf("want 5990 after settle, got %d", u.BalanceSeconds)
	}
	var tu store.TrialUsage
	if err := db.First(&tu, "user_id = ?", "u3").Error; err != nil || tu.UsedSeconds != 20 {
		t.Fatalf("want trial_usage 20, got %+v err=%v", tu, err)
	}
	_ = rdb
}
