package packagepkg

import (
	"context"
	"strconv"
	"testing"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/testutil"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// testIDMask 用于将纳秒时间戳截断为合法的 uint 主键范围，仅限测试用。
const testIDMask = 0x7fffffff

// TestActivateByRealnameUsesPurchasedSnapshot 验证实名激活只使用购买快照计算起止时间。
func TestActivateByRealnameUsesPurchasedSnapshot(t *testing.T) {
	tx := testutil.NewPostgresTransaction(t)
	card := createActivationTermsCard(t, tx, constants.RealNameStatusVerified)
	pkg := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 99)
	purchasedAt := time.Now().Add(-48 * time.Hour).Truncate(time.Second)
	usage := createActivationTermsUsage(t, tx, pkg.ID, card.ID, 1, constants.PackageExpiryBaseFromPurchase, 10, true, purchasedAt)
	service := NewActivationService(tx, nil, nil, nil, nil, zap.NewNop())
	if err := service.ActivateByRealname(context.Background(), constants.AssetTypeIotCard, card.ID); err != nil {
		t.Fatalf("实名激活失败：%v", err)
	}
	var refreshed model.PackageUsage
	if err := tx.First(&refreshed, usage.ID).Error; err != nil {
		t.Fatalf("查询激活结果失败：%v", err)
	}
	expectedExpiry := CalculateExpiryTime(constants.PackageCalendarTypeByDay, refreshed.CreatedAt, 0, 10)
	if refreshed.ActivatedAt == nil || refreshed.ExpiresAt == nil || refreshed.ActivatedAt.Unix() != refreshed.CreatedAt.Unix() || refreshed.ExpiresAt.Unix() != expectedExpiry.Unix() {
		t.Fatalf("实名激活未使用购买快照：%+v", refreshed)
	}
}

// TestActivateNextPendingMainPackageUsesQueueSnapshotAndIsIdempotent 验证连续排队按优先级和各自快照接续。
func TestActivateNextPendingMainPackageUsesQueueSnapshotAndIsIdempotent(t *testing.T) {
	tx := testutil.NewPostgresTransaction(t)
	redisClient := testutil.NewRedisClient(t)
	card := createActivationTermsCard(t, tx, constants.RealNameStatusVerified)
	firstPackage := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 90)
	secondPackage := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 90)
	first := createActivationTermsUsage(t, tx, firstPackage.ID, card.ID, 1, constants.PackageExpiryBaseFromActivation, 3, false, time.Now().Add(-time.Hour))
	second := createActivationTermsUsage(t, tx, secondPackage.ID, card.ID, 2, constants.PackageExpiryBaseFromActivation, 4, false, time.Now())
	service := NewActivationService(tx, redisClient, nil, nil, nil, zap.NewNop())
	activated, err := service.ActivateNextPendingMainPackage(context.Background(), constants.AssetTypeIotCard, card.ID)
	if err != nil || !activated {
		t.Fatalf("激活队首套餐失败：activated=%v err=%v", activated, err)
	}
	activated, err = service.ActivateNextPendingMainPackage(context.Background(), constants.AssetTypeIotCard, card.ID)
	if err != nil || activated {
		t.Fatalf("已有生效主套餐时重复接续应幂等：activated=%v err=%v", activated, err)
	}
	if err := tx.Model(&model.PackageUsage{}).Where("id = ?", first.ID).Update("status", constants.PackageUsageStatusExpired).Error; err != nil {
		t.Fatalf("结束队首套餐失败：%v", err)
	}
	activated, err = service.ActivateNextPendingMainPackage(context.Background(), constants.AssetTypeIotCard, card.ID)
	if err != nil || !activated {
		t.Fatalf("激活第二个排队套餐失败：activated=%v err=%v", activated, err)
	}
	var refreshed []model.PackageUsage
	if err := tx.Where("id IN ?", []uint{first.ID, second.ID}).Order("priority").Find(&refreshed).Error; err != nil {
		t.Fatalf("查询队列接续结果失败：%v", err)
	}
	if len(refreshed) != 2 || refreshed[1].ExpiresAt == nil || refreshed[1].ActivatedAt == nil || !refreshed[1].ExpiresAt.Equal(CalculateExpiryTime(constants.PackageCalendarTypeByDay, *refreshed[1].ActivatedAt, 0, 4)) {
		t.Fatalf("第二个排队套餐未使用自身快照：%+v", refreshed)
	}
}

// TestRefundFollowUpActivatesNextPackageFromSnapshot 验证退款失效后下一套餐按购买快照接续。
func TestRefundFollowUpActivatesNextPackageFromSnapshot(t *testing.T) {
	tx := testutil.NewPostgresTransaction(t)
	redisClient := testutil.NewRedisClient(t)
	card := createActivationTermsCard(t, tx, constants.RealNameStatusVerified)
	currentPackage := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 60)
	nextPackage := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 60)
	current := createActivationTermsUsage(t, tx, currentPackage.ID, card.ID, 1, constants.PackageExpiryBaseFromPurchase, 30, false, time.Now().Add(-time.Hour))
	now := time.Now()
	if err := tx.Model(current).Updates(map[string]any{"status": constants.PackageUsageStatusActive, "activated_at": now, "expires_at": now.AddDate(0, 0, 30)}).Error; err != nil {
		t.Fatalf("设置当前生效套餐失败：%v", err)
	}
	next := createActivationTermsUsage(t, tx, nextPackage.ID, card.ID, 2, constants.PackageExpiryBaseFromActivation, 6, false, time.Now())
	service := NewActivationService(tx, redisClient, nil, nil, nil, zap.NewNop())
	if err := service.InvalidatePackagesForRefund(context.Background(), constants.AssetTypeIotCard, card.ID, current.OrderID, 77, "UR55-REFUND", &current.ID); err != nil {
		t.Fatalf("退款失效当前套餐失败：%v", err)
	}
	activated, err := service.ActivateNextPendingMainPackage(context.Background(), constants.AssetTypeIotCard, card.ID)
	if err != nil || !activated {
		t.Fatalf("退款后接续下一套餐失败：activated=%v err=%v", activated, err)
	}
	var refreshed model.PackageUsage
	if err := tx.First(&refreshed, next.ID).Error; err != nil {
		t.Fatalf("查询退款接续结果失败：%v", err)
	}
	if refreshed.ExpiresAt == nil || refreshed.ActivatedAt == nil || !refreshed.ExpiresAt.Equal(CalculateExpiryTime(constants.PackageCalendarTypeByDay, *refreshed.ActivatedAt, 0, 6)) {
		t.Fatalf("退款接续未使用下一套餐快照：%+v", refreshed)
	}
}

// TestFromPurchaseActivatesImmediatelyAtPurchaseTime 验证 from_purchase 快照在购买时直接激活，不等实名。
func TestFromPurchaseActivatesImmediatelyAtPurchaseTime(t *testing.T) {
	tx := testutil.NewPostgresTransaction(t)
	redisClient := testutil.NewRedisClient(t)
	card := createActivationTermsCard(t, tx, constants.RealNameStatusNotVerified)
	pkg := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 7)
	usage := createActivationTermsUsage(t, tx, pkg.ID, card.ID, 1, constants.PackageExpiryBaseFromPurchase, 7, false, time.Now())
	service := NewActivationService(tx, redisClient, nil, nil, nil, zap.NewNop())
	if err := service.ActivateSpecificPackage(context.Background(), usage.ID); err != nil {
		t.Fatalf("from_purchase 直接激活失败：%v", err)
	}
	var refreshed model.PackageUsage
	if err := tx.First(&refreshed, usage.ID).Error; err != nil {
		t.Fatalf("查询激活结果失败：%v", err)
	}
	if refreshed.Status != constants.PackageUsageStatusActive || refreshed.ActivatedAt == nil || refreshed.ExpiresAt == nil {
		t.Fatalf("from_purchase 套餐应已激活：status=%d activatedAt=%v expiresAt=%v", refreshed.Status, refreshed.ActivatedAt, refreshed.ExpiresAt)
	}
	if refreshed.PendingRealnameActivation {
		t.Fatal("from_purchase 不应标记待实名")
	}
	expectedExpiry := CalculateExpiryTime(constants.PackageCalendarTypeByDay, *refreshed.ActivatedAt, 0, 7)
	if !refreshed.ExpiresAt.Equal(expectedExpiry) {
		t.Fatalf("to期时间不符：want=%v got=%v", expectedExpiry, refreshed.ExpiresAt)
	}
}

// TestHistoricalFallbackFiresWarningAndIncrementsCounter 验证历史记录缺少快照时回退套餐当前配置并递增计数器。
func TestHistoricalFallbackFiresWarningAndIncrementsCounter(t *testing.T) {
	tx := testutil.NewPostgresTransaction(t)
	redisClient := testutil.NewRedisClient(t)
	card := createActivationTermsCard(t, tx, constants.RealNameStatusVerified)
	pkg := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromActivation, 21)
	// 仅在会回滚的测试事务内禁用触发器，以构造 UR#55 上线前的历史空快照记录。
	if err := tx.Exec("ALTER TABLE tb_package_usage DISABLE TRIGGER trg_validate_package_usage_terms_snapshot").Error; err != nil {
		t.Fatalf("禁用快照校验触发器失败：%v", err)
	}
	// 历史记录：四个快照字段全为空值/零值
	unique := uint(time.Now().UnixNano() & testIDMask)
	usage := &model.PackageUsage{
		OrderID: unique, OrderNo: "UR55-HIST-" + strconv.FormatUint(uint64(unique), 10),
		PackageID: pkg.ID, PackageName: "UR55历史测试套餐",
		UsageType: constants.AssetWalletResourceTypeIotCard, IotCardID: card.ID,
		DataLimitMB: 1, Status: constants.PackageUsageStatusPending, Priority: 1, Generation: 1,
		// 快照字段均留空，模拟 UR#55 上线前的旧数据
	}
	if err := tx.Omit("status").Create(usage).Error; err != nil {
		t.Fatalf("创建历史测试使用记录失败：%v", err)
	}
	if err := tx.Model(usage).Update("status", constants.PackageUsageStatusPending).Error; err != nil {
		t.Fatalf("设置历史测试状态失败：%v", err)
	}
	before := HistoricalTermsFallbackCount()
	service := NewActivationService(tx, redisClient, nil, nil, nil, zap.NewNop())
	if err := service.ActivateSpecificPackage(context.Background(), usage.ID); err != nil {
		t.Fatalf("历史记录兼容激活失败：%v", err)
	}
	after := HistoricalTermsFallbackCount()
	if after <= before {
		t.Fatalf("历史回退计数器未递增：before=%d after=%d", before, after)
	}
	var refreshed model.PackageUsage
	if err := tx.First(&refreshed, usage.ID).Error; err != nil || refreshed.Status != constants.PackageUsageStatusActive {
		t.Fatalf("历史记录应兼容激活成功：status=%d err=%v", refreshed.Status, err)
	}
}

// TestActivateSpecificPackageRejectsPartialSnapshot 验证新记录非法快照不会静默回退当前套餐配置。
func TestActivateSpecificPackageRejectsPartialSnapshot(t *testing.T) {
	tx := testutil.NewPostgresTransaction(t)
	redisClient := testutil.NewRedisClient(t)
	card := createActivationTermsCard(t, tx, constants.RealNameStatusVerified)
	pkg := createActivationTermsPackage(t, tx, constants.PackageExpiryBaseFromPurchase, 30)
	usage := createActivationTermsUsage(t, tx, pkg.ID, card.ID, 1, constants.PackageExpiryBaseFromPurchase, 30, false, time.Now())
	if err := tx.Exec("ALTER TABLE tb_package_usage DISABLE TRIGGER trg_validate_package_usage_terms_snapshot").Error; err != nil {
		t.Fatalf("禁用快照校验触发器失败：%v", err)
	}
	if err := tx.Model(usage).Update("calendar_type_snapshot", "invalid").Error; err != nil {
		t.Fatalf("构造非法快照失败：%v", err)
	}
	if err := tx.Exec("ALTER TABLE tb_package_usage ENABLE TRIGGER trg_validate_package_usage_terms_snapshot").Error; err != nil {
		t.Fatalf("恢复快照校验触发器失败：%v", err)
	}
	service := NewActivationService(tx, redisClient, nil, nil, nil, zap.NewNop())
	if err := service.ActivateSpecificPackage(context.Background(), usage.ID); err == nil {
		t.Fatal("非法部分快照必须拒绝激活")
	}
	var refreshed model.PackageUsage
	if err := tx.First(&refreshed, usage.ID).Error; err != nil || refreshed.Status != constants.PackageUsageStatusPending {
		t.Fatalf("非法快照失败后状态必须保持待生效：status=%d err=%v", refreshed.Status, err)
	}
}

func createActivationTermsCard(t *testing.T, tx *gorm.DB, realnameStatus int) *model.IotCard {
	t.Helper()
	suffix := strconv.FormatInt(time.Now().UnixNano()%100000000000000000, 10)
	iccid := "87" + suffix
	if len(iccid) < 19 {
		iccid += "0000000000000000000"[:19-len(iccid)]
	}
	iccid = iccid[:19]
	card := &model.IotCard{ICCID: iccid, ICCID19: iccid, CarrierID: 1, RealNameStatus: realnameStatus, AssetStatus: constants.AssetStatusInStock, Generation: 1}
	if err := tx.Create(card).Error; err != nil {
		t.Fatalf("创建激活测试卡失败：%v", err)
	}
	return card
}

func createActivationTermsPackage(t *testing.T, tx *gorm.DB, expiryBase string, durationDays int) *model.Package {
	t.Helper()
	suffix := strconv.FormatInt(time.Now().UnixNano(), 10)
	pkg := &model.Package{PackageCode: "UR55-ACTIVATE-" + suffix, PackageName: "UR55激活测试套餐", PackageType: constants.PackageTypeFormal, DurationMonths: 1, DurationDays: durationDays, CalendarType: constants.PackageCalendarTypeByDay, ExpiryBase: expiryBase, Status: constants.StatusEnabled, ShelfStatus: 1, DataResetCycle: "monthly"}
	if err := tx.Create(pkg).Error; err != nil {
		t.Fatalf("创建激活测试套餐失败：%v", err)
	}
	return pkg
}

func createActivationTermsUsage(t *testing.T, tx *gorm.DB, packageID, cardID uint, priority int, expiryBase string, durationDays int, pendingRealname bool, createdAt time.Time) *model.PackageUsage {
	t.Helper()
	unique := uint(time.Now().UnixNano() & testIDMask)
	usage := &model.PackageUsage{Model: gorm.Model{CreatedAt: createdAt}, OrderID: unique, OrderNo: "UR55-ACTIVATE-" + strconv.FormatUint(uint64(unique), 10), PackageID: packageID, PackageName: "UR55激活测试套餐", UsageType: constants.AssetWalletResourceTypeIotCard, IotCardID: cardID, DataLimitMB: 1, Status: constants.PackageUsageStatusPending, Priority: priority, PendingRealnameActivation: pendingRealname, Generation: 1, ExpiryBaseSnapshot: expiryBase, CalendarTypeSnapshot: constants.PackageCalendarTypeByDay, DurationDaysSnapshot: durationDays}
	if err := tx.Omit("status", "pending_realname_activation").Create(usage).Error; err != nil {
		t.Fatalf("创建激活测试使用记录失败：%v", err)
	}
	if err := tx.Model(usage).Updates(map[string]any{"status": constants.PackageUsageStatusPending, "pending_realname_activation": pendingRealname}).Error; err != nil {
		t.Fatalf("设置激活测试状态失败：%v", err)
	}
	return usage
}
