package model

import (
	"testing"

	"github.com/QuantumNous/new-api/common"
	"github.com/glebarez/sqlite"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"
)

func setupRedemptionDB(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	sqlDB, _ := db.DB()
	sqlDB.SetMaxOpenConns(1)

	origDB := DB
	origLogDB := LOG_DB
	DB = db
	LOG_DB = db
	common.UsingSQLite = true
	common.RedisEnabled = false

	require.NoError(t, db.AutoMigrate(&User{}, &Redemption{}, &Log{}))

	t.Cleanup(func() {
		DB = origDB
		LOG_DB = origLogDB
		sqlDB.Close()
	})
	return db
}

func TestRedeem_Success(t *testing.T) {
	db := setupRedemptionDB(t)

	// 创建测试用户
	user := User{
		Id:       100,
		Username: "redeem_test",
		Password: "hashed_password",
		Quota:    100000,
	}
	require.NoError(t, db.Create(&user).Error)

	// 创建兑换码
	redemption := Redemption{
		Id:          1,
		UserId:      1,
		Key:         "test-key-123",
		Status:      common.RedemptionCodeStatusEnabled,
		Quota:       50000,
		CreatedTime: common.GetTimestamp(),
		ExpiredTime: 0, // 永不过期
	}
	require.NoError(t, db.Create(&redemption).Error)

	// 设置回调
	callbackCalled := false
	var receivedUserId int
	var receivedQuota int

	SetQuotaUpdateCallback(func(userId int, quota int) {
		callbackCalled = true
		receivedUserId = userId
		receivedQuota = quota
	})
	defer SetQuotaUpdateCallback(nil)

	// 执行兑换
	quota, err := Redeem("test-key-123", 100)
	require.NoError(t, err)
	assert.Equal(t, 50000, quota)

	// 验证回调被触发
	assert.True(t, callbackCalled)
	assert.Equal(t, 100, receivedUserId)
	assert.Equal(t, 150000, receivedQuota) // 原始 100000 + 兑换 50000

	// 验证用户额度已更新
	var updatedUser User
	require.NoError(t, db.First(&updatedUser, 100).Error)
	assert.Equal(t, 150000, updatedUser.Quota)

	// 验证兑换码状态已更新
	var updatedRedemption Redemption
	require.NoError(t, db.First(&updatedRedemption, 1).Error)
	assert.Equal(t, common.RedemptionCodeStatusUsed, updatedRedemption.Status)
	assert.Equal(t, 100, updatedRedemption.UsedUserId)
}

func TestRedeem_InvalidKey(t *testing.T) {
	setupRedemptionDB(t)

	// 测试无效的兑换码
	quota, err := Redeem("invalid-key", 100)
	assert.Error(t, err)
	assert.Equal(t, 0, quota)
}

func TestRedeem_AlreadyUsed(t *testing.T) {
	db := setupRedemptionDB(t)

	// 创建测试用户
	user := User{
		Id:       100,
		Username: "redeem_used_test",
		Password: "hashed_password",
		Quota:    100000,
	}
	require.NoError(t, db.Create(&user).Error)

	// 创建已使用的兑换码
	redemption := Redemption{
		Id:           1,
		UserId:       1,
		Key:          "used-key",
		Status:       common.RedemptionCodeStatusUsed,
		Quota:        50000,
		CreatedTime:  common.GetTimestamp(),
		RedeemedTime: common.GetTimestamp(),
		UsedUserId:   99,
	}
	require.NoError(t, db.Create(&redemption).Error)

	// 尝试兑换已使用的兑换码
	quota, err := Redeem("used-key", 100)
	assert.Error(t, err)
	assert.Equal(t, 0, quota)
	// 错误会被包装为 ErrRedeemFailed，但底层错误消息会记录在日志中
	assert.Equal(t, ErrRedeemFailed, err)
}

func TestRedeem_Expired(t *testing.T) {
	db := setupRedemptionDB(t)

	// 创建测试用户
	user := User{
		Id:       100,
		Username: "redeem_expired_test",
		Password: "hashed_password",
		Quota:    100000,
	}
	require.NoError(t, db.Create(&user).Error)

	// 创建已过期的兑换码
	redemption := Redemption{
		Id:          1,
		UserId:      1,
		Key:         "expired-key",
		Status:      common.RedemptionCodeStatusEnabled,
		Quota:       50000,
		CreatedTime: common.GetTimestamp() - 86400,
		ExpiredTime: common.GetTimestamp() - 3600, // 已过期
	}
	require.NoError(t, db.Create(&redemption).Error)

	// 尝试兑换已过期的兑换码
	quota, err := Redeem("expired-key", 100)
	assert.Error(t, err)
	assert.Equal(t, 0, quota)
	// 错误会被包装为 ErrRedeemFailed
	assert.Equal(t, ErrRedeemFailed, err)
}

func TestRedeem_CallbackPanic(t *testing.T) {
	db := setupRedemptionDB(t)

	// 创建测试用户
	user := User{
		Id:       100,
		Username: "redeem_panic_test",
		Password: "hashed_password",
		Quota:    100000,
	}
	require.NoError(t, db.Create(&user).Error)

	// 创建兑换码
	redemption := Redemption{
		Id:          1,
		UserId:      1,
		Key:         "panic-key",
		Status:      common.RedemptionCodeStatusEnabled,
		Quota:       50000,
		CreatedTime: common.GetTimestamp(),
	}
	require.NoError(t, db.Create(&redemption).Error)

	// 设置一个会 panic 的回调
	SetQuotaUpdateCallback(func(userId int, quota int) {
		panic("callback panic in redeem")
	})
	defer SetQuotaUpdateCallback(nil)

	// 执行兑换（回调 panic 不应影响兑换成功）
	quota, err := Redeem("panic-key", 100)
	require.NoError(t, err)
	assert.Equal(t, 50000, quota)

	// 验证用户额度已更新
	var updatedUser User
	require.NoError(t, db.First(&updatedUser, 100).Error)
	assert.Equal(t, 150000, updatedUser.Quota)
}

func TestRedeem_SyncedUser(t *testing.T) {
	db := setupRedemptionDB(t)

	// 创建同步用户（从 Master 节点同步过来的）
	user := User{
		Id:           100,
		Username:     "synced_redeem_test",
		Password:     "hashed_password",
		Quota:        100000,
		Source:       common.UserSourceSynced,
		RemoteUserId: 1000,
		SyncedQuota:  100000,
	}
	require.NoError(t, db.Create(&user).Error)

	// 创建兑换码
	redemption := Redemption{
		Id:          1,
		UserId:      1,
		Key:         "synced-redeem-key",
		Status:      common.RedemptionCodeStatusEnabled,
		Quota:       50000,
		CreatedTime: common.GetTimestamp(),
	}
	require.NoError(t, db.Create(&redemption).Error)

	// 设置回调
	callbackCalled := false
	SetQuotaUpdateCallback(func(userId int, quota int) {
		callbackCalled = true
	})
	defer SetQuotaUpdateCallback(nil)

	// 执行兑换
	quota, err := Redeem("synced-redeem-key", 100)
	require.NoError(t, err)
	assert.Equal(t, 50000, quota)

	// 验证回调被触发（Master 节点需要同步到 Slave）
	assert.True(t, callbackCalled)

	// 验证用户额度已更新
	var updatedUser User
	require.NoError(t, db.First(&updatedUser, 100).Error)
	assert.Equal(t, 150000, updatedUser.Quota)
}

func TestRedemptionUpdateRemarkPersistsRemark(t *testing.T) {
	db := setupRedemptionDB(t)

	redemption := Redemption{
		Id:          1,
		UserId:      1,
		Key:         "remark-update-key",
		Status:      common.RedemptionCodeStatusEnabled,
		Name:        "starter",
		Remark:      "initial-note",
		Quota:       100,
		CreatedTime: common.GetTimestamp(),
	}
	require.NoError(t, db.Create(&redemption).Error)

	redemption.Remark = "vip-updated"
	require.NoError(t, redemption.Update())

	var updated Redemption
	require.NoError(t, db.First(&updated, redemption.Id).Error)
	assert.Equal(t, "vip-updated", updated.Remark)
}

func TestSearchRedemptionsByRemark(t *testing.T) {
	db := setupRedemptionDB(t)

	require.NoError(t, db.Create(&Redemption{
		Id:          1,
		UserId:      1,
		Key:         "remark-search-key",
		Status:      common.RedemptionCodeStatusEnabled,
		Name:        "starter-pack",
		Remark:      "vip benefit",
		Quota:       100,
		CreatedTime: common.GetTimestamp(),
	}).Error)
	require.NoError(t, db.Create(&Redemption{
		Id:          2,
		UserId:      1,
		Key:         "remark-search-other",
		Status:      common.RedemptionCodeStatusEnabled,
		Name:        "basic-pack",
		Remark:      "standard benefit",
		Quota:       100,
		CreatedTime: common.GetTimestamp(),
	}).Error)

	results, total, err := SearchRedemptions("vip", 0, 10)
	require.NoError(t, err)
	require.Equal(t, int64(1), total)
	require.Len(t, results, 1)
	assert.Equal(t, 1, results[0].Id)
	assert.Equal(t, "vip benefit", results[0].Remark)
}

func TestSearchRedemptionsByRemarkWithNumericKeyword(t *testing.T) {
	db := setupRedemptionDB(t)

	require.NoError(t, db.Create(&Redemption{
		Id:          1,
		UserId:      1,
		Key:         "remark-search-numeric",
		Status:      common.RedemptionCodeStatusEnabled,
		Name:        "starter-pack",
		Remark:      "123-vip benefit",
		Quota:       100,
		CreatedTime: common.GetTimestamp(),
	}).Error)
	require.NoError(t, db.Create(&Redemption{
		Id:          2,
		UserId:      1,
		Key:         "remark-search-other-numeric",
		Status:      common.RedemptionCodeStatusEnabled,
		Name:        "basic-pack",
		Remark:      "standard benefit",
		Quota:       100,
		CreatedTime: common.GetTimestamp(),
	}).Error)

	results, total, err := SearchRedemptions("123", 0, 10)
	require.NoError(t, err)
	require.Equal(t, int64(1), total)
	require.Len(t, results, 1)
	assert.Equal(t, 1, results[0].Id)
	assert.Equal(t, "123-vip benefit", results[0].Remark)
}
