package packagepkg

import (
	"context"
	"math"
	"strconv"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	// usageRemainderTTL 流量小数余量缓存时长
	usageRemainderTTL = 7 * 24 * time.Hour
	// usageRemainderEpsilon 浮点运算容差
	usageRemainderEpsilon = 1e-9
)

// StopResumeCallback 任务 24.6: 停复机回调接口
// 用于在流量用完时触发停机操作
type StopResumeCallback interface {
	// CheckAndStopCard 检查流量耗尽并停机
	CheckAndStopCard(ctx context.Context, cardID uint) error
}

type UsageService struct {
	db                      *gorm.DB
	redis                   *redis.Client
	packageUsageStore       *postgres.PackageUsageStore
	packageUsageDailyRecord *postgres.PackageUsageDailyRecordStore
	deviceSimBindingStore   *postgres.DeviceSimBindingStore
	logger                  *zap.Logger
	stopResumeCallback      StopResumeCallback // 停复机回调，可选
}

func NewUsageService(
	db *gorm.DB,
	redis *redis.Client,
	packageUsageStore *postgres.PackageUsageStore,
	packageUsageDailyRecord *postgres.PackageUsageDailyRecordStore,
	deviceSimBindingStore *postgres.DeviceSimBindingStore,
	logger *zap.Logger,
) *UsageService {
	return &UsageService{
		db:                      db,
		redis:                   redis,
		packageUsageStore:       packageUsageStore,
		packageUsageDailyRecord: packageUsageDailyRecord,
		deviceSimBindingStore:   deviceSimBindingStore,
		logger:                  logger,
	}
}

// SetStopResumeCallback 任务 24.6: 设置停复机回调
// 在应用启动时由 bootstrap 调用，注入停复机服务
func (s *UsageService) SetStopResumeCallback(callback StopResumeCallback) {
	s.stopResumeCallback = callback
}

// DeductDataUsage 任务 10.2-10.6: 按优先级扣减流量
// 扣减顺序：加油包（按 priority ASC） → 主套餐
// 真实用量允许超过套餐阈值，超额部分记入最后一个生效套餐，用于后续虚流量阈值评估。
// 流量用完时自动标记 status=2，所有套餐用完时触发停机。
func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string, carrierID uint, usageMB float64) error {
	if usageMB <= 0 {
		return errors.New(errors.CodeInvalidParam, "扣减流量必须大于0")
	}

	deductUsageMB := s.convertToDeductMB(ctx, carrierType, carrierID, usageMB)
	if deductUsageMB <= 0 {
		return nil
	}

	shouldSuspend := false
	suspendCarrierType := ""
	var suspendCarrierID uint

	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		targetCarrierType, targetCarrierID, packages, err := s.resolveActivePackages(ctx, tx, carrierType, carrierID)
		if err != nil {
			return err
		}

		today := time.Now().Format("2006-01-02")

		if len(packages) == 0 {
			// 无生效套餐：尝试找 Depleted 套餐记录溢出流量详单，避免详单断档
			if recordErr := s.recordOverflowToDepletedPackage(ctx, tx, targetCarrierType, targetCarrierID, deductUsageMB, today); recordErr != nil {
				s.logger.Warn("记录溢出流量到已耗尽套餐失败",
					zap.String("carrier_type", targetCarrierType),
					zap.Uint("carrier_id", targetCarrierID),
					zap.Int64("usage_mb", deductUsageMB),
					zap.Error(recordErr))
			}
			return errors.New(errors.CodeNoAvailablePackage, "没有可用套餐")
		}

		// 按优先级扣减流量
		remainingUsage := deductUsageMB

		for index, pkg := range packages {
			if remainingUsage <= 0 {
				break
			}

			// 计算当前套餐剩余额度
			effectiveThresholdMB := pkg.EffectiveThresholdMB()
			remainingQuota := pkg.RemainingQuotaMB()
			isLastPackage := index == len(packages)-1
			if remainingQuota <= 0 && !isLastPackage {
				// 套餐已用完，标记为已用完
				if err := tx.Model(pkg).Update("status", constants.PackageUsageStatusDepleted).Error; err != nil {
					return errors.Wrap(errors.CodeDatabaseError, err, "更新套餐状态失败")
				}
				continue
			}

			// 本次从该套餐扣减的流量
			var deductFromPkg int64
			if isLastPackage {
				deductFromPkg = remainingUsage
			} else if remainingUsage <= remainingQuota {
				deductFromPkg = remainingUsage
			} else {
				deductFromPkg = remainingQuota
			}
			if deductFromPkg <= 0 {
				if err := tx.Model(pkg).Update("status", constants.PackageUsageStatusDepleted).Error; err != nil {
					return errors.Wrap(errors.CodeDatabaseError, err, "更新套餐状态失败")
				}
				continue
			}

			// 更新套餐使用量
			newUsage := pkg.DataUsageMB + deductFromPkg
			updates := map[string]interface{}{
				"data_usage_mb": newUsage,
			}

			// 检查是否用完
			if effectiveThresholdMB > 0 && newUsage >= effectiveThresholdMB {
				updates["status"] = constants.PackageUsageStatusDepleted
			}

			if err := tx.Model(pkg).Updates(updates).Error; err != nil {
				return errors.Wrap(errors.CodeDatabaseError, err, "更新套餐使用量失败")
			}

			// 任务 10.6: 写入日记录
			if err := s.updateDailyRecord(ctx, tx, pkg.ID, today, deductFromPkg, newUsage); err != nil {
				return err
			}

			remainingUsage -= deductFromPkg

			s.logger.Info("扣减套餐流量",
				zap.Uint("usage_id", pkg.ID),
				zap.Int64("deduct_mb", deductFromPkg),
				zap.Int64("new_usage_mb", newUsage),
				zap.Int64("effective_threshold_mb", effectiveThresholdMB),
				zap.Int64("data_limit_mb", pkg.DataLimitMB),
				zap.Int64("remaining_quota_mb", remainingQuota),
				zap.Bool("over_threshold", effectiveThresholdMB > 0 && newUsage > effectiveThresholdMB))
		}

		// 任务 10.5: 检查是否所有套餐都用完（触发停机）
		shouldSuspendCurrent, err := s.checkAndTriggerSuspension(ctx, tx, targetCarrierType, targetCarrierID)
		if err != nil {
			return err
		}
		shouldSuspend = shouldSuspendCurrent
		suspendCarrierType = targetCarrierType
		suspendCarrierID = targetCarrierID

		return nil
	})
	if err != nil {
		return err
	}

	if shouldSuspend {
		s.triggerSuspensionAfterCommit(ctx, suspendCarrierType, suspendCarrierID)
	}

	return nil
}

// convertToDeductMB 将浮点增量转换为可扣减的整数 MB
// 规则：先合并 Redis 中的小数余量，再按整数 MB 扣减；余数回写 Redis
func (s *UsageService) convertToDeductMB(ctx context.Context, carrierType string, carrierID uint, usageMB float64) int64 {
	deductUsageMB := int64(math.Floor(usageMB + usageRemainderEpsilon))
	remainder := usageMB - float64(deductUsageMB)

	if s.redis == nil {
		return deductUsageMB
	}

	remainderKey := constants.RedisPackageUsageRemainderKey(carrierType, carrierID)
	cachedRemainder, err := s.getUsageRemainder(ctx, remainderKey)
	if err != nil {
		s.logger.Warn("读取流量小数余量失败，降级为本次直接取整扣减",
			zap.String("carrier_type", carrierType),
			zap.Uint("carrier_id", carrierID),
			zap.Float64("usage_mb", usageMB),
			zap.Error(err))
		return deductUsageMB
	}

	totalUsage := usageMB + cachedRemainder
	deductUsageMB = int64(math.Floor(totalUsage + usageRemainderEpsilon))
	remainder = totalUsage - float64(deductUsageMB)
	if remainder < usageRemainderEpsilon {
		remainder = 0
	}

	if err := s.persistUsageRemainder(ctx, remainderKey, remainder); err != nil {
		s.logger.Warn("写入流量小数余量失败，本次仅按整数扣减",
			zap.String("carrier_type", carrierType),
			zap.Uint("carrier_id", carrierID),
			zap.Float64("remainder_mb", remainder),
			zap.Error(err))
	}

	return deductUsageMB
}

// getUsageRemainder 读取载体流量小数余量
func (s *UsageService) getUsageRemainder(ctx context.Context, key string) (float64, error) {
	value, err := s.redis.Get(ctx, key).Result()
	if err == redis.Nil {
		return 0, nil
	}
	if err != nil {
		return 0, err
	}
	remainder, parseErr := strconv.ParseFloat(value, 64)
	if parseErr != nil {
		// 脏值直接删除，避免持续影响后续扣减
		_ = s.redis.Del(ctx, key).Err()
		return 0, parseErr
	}
	return remainder, nil
}

// persistUsageRemainder 持久化载体流量小数余量
func (s *UsageService) persistUsageRemainder(ctx context.Context, key string, remainder float64) error {
	if remainder <= usageRemainderEpsilon {
		return s.redis.Del(ctx, key).Err()
	}
	value := strconv.FormatFloat(remainder, 'f', 6, 64)
	return s.redis.Set(ctx, key, value, usageRemainderTTL).Err()
}

// resolveActivePackages 统一解析可扣减载体
// 优先按请求载体查找；当 iot_card 无生效套餐且存在设备绑定时，回退到 device 载体
func (s *UsageService) resolveActivePackages(ctx context.Context, tx *gorm.DB, carrierType string, carrierID uint) (string, uint, []*model.PackageUsage, error) {
	packages, err := s.queryActivePackages(tx, carrierType, carrierID)
	if err != nil {
		return "", 0, nil, err
	}
	if len(packages) > 0 {
		return carrierType, carrierID, packages, nil
	}

	if carrierType != constants.AssetTypeIotCard {
		return carrierType, carrierID, packages, nil
	}

	binding, err := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, carrierID)
	if err == gorm.ErrRecordNotFound {
		return carrierType, carrierID, packages, nil
	}
	if err != nil {
		return "", 0, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询卡绑定设备失败")
	}

	devicePackages, queryErr := s.queryActivePackages(tx, constants.AssetTypeDevice, binding.DeviceID)
	if queryErr != nil {
		return "", 0, nil, queryErr
	}
	if len(devicePackages) == 0 {
		return carrierType, carrierID, packages, nil
	}

	s.logger.Info("卡载体无生效套餐，回退设备载体扣减流量",
		zap.Uint("card_id", carrierID),
		zap.Uint("device_id", binding.DeviceID))

	return constants.AssetTypeDevice, binding.DeviceID, devicePackages, nil
}

// queryActivePackages 查询指定载体下的生效套餐（按扣减优先级排序）
func (s *UsageService) queryActivePackages(tx *gorm.DB, carrierType string, carrierID uint) ([]*model.PackageUsage, error) {
	query := tx.Where("status = ?", constants.PackageUsageStatusActive)

	switch carrierType {
	case constants.AssetTypeIotCard:
		query = query.Where("iot_card_id = ?", carrierID)
	case constants.AssetTypeDevice:
		query = query.Where("device_id = ?", carrierID)
	default:
		return nil, errors.New(errors.CodeInvalidParam, "无效的载体类型")
	}

	var packages []*model.PackageUsage
	if err := query.
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Order("CASE WHEN master_usage_id IS NOT NULL THEN 0 ELSE 1 END, priority ASC, id ASC").
		Find(&packages).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询生效套餐失败")
	}
	return packages, nil
}

// updateDailyRecord 任务 10.6: 更新日流量记录
func (s *UsageService) updateDailyRecord(ctx context.Context, tx *gorm.DB, packageUsageID uint, dateStr string, dailyUsageMB, cumulativeUsageMB int64) error {
	// 解析日期字符串
	date, err := time.Parse("2006-01-02", dateStr)
	if err != nil {
		return errors.Wrap(errors.CodeInvalidParam, err, "日期格式错误")
	}

	// 查询是否已有今日记录
	var record model.PackageUsageDailyRecord
	err = tx.Where("package_usage_id = ? AND date = ?", packageUsageID, date).
		First(&record).Error

	if err == gorm.ErrRecordNotFound {
		// 创建新记录
		record = model.PackageUsageDailyRecord{
			PackageUsageID:    packageUsageID,
			Date:              date,
			DailyUsageMB:      int(dailyUsageMB),
			CumulativeUsageMB: cumulativeUsageMB,
		}
		if err := tx.Create(&record).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "创建日流量记录失败")
		}
	} else if err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "查询日流量记录失败")
	} else {
		// 更新现有记录
		updates := map[string]interface{}{
			"daily_usage_mb":      record.DailyUsageMB + int(dailyUsageMB),
			"cumulative_usage_mb": cumulativeUsageMB,
		}
		if err := tx.Model(&record).Updates(updates).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "更新日流量记录失败")
		}
	}

	return nil
}

// checkAndTriggerSuspension 任务 10.5: 检查停机条件
func (s *UsageService) checkAndTriggerSuspension(ctx context.Context, tx *gorm.DB, carrierType string, carrierID uint) (bool, error) {
	query := tx.Model(&model.PackageUsage{}).
		Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted})

	if carrierType == constants.AssetTypeIotCard {
		query = query.Where("iot_card_id = ?", carrierID)
	} else if carrierType == constants.AssetTypeDevice {
		query = query.Where("device_id = ?", carrierID)
	} else {
		return false, errors.New(errors.CodeInvalidParam, "无效的载体类型")
	}

	var packages []*model.PackageUsage
	if err := query.Find(&packages).Error; err != nil {
		return false, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐状态失败")
	}

	hasAvailablePackage := false
	for _, pkg := range packages {
		if pkg == nil {
			continue
		}
		if pkg.IsTrafficDepleted() {
			if pkg.Status != constants.PackageUsageStatusDepleted {
				if err := tx.Model(pkg).Update("status", constants.PackageUsageStatusDepleted).Error; err != nil {
					return false, errors.Wrap(errors.CodeDatabaseError, err, "更新套餐耗尽状态失败")
				}
			}
			continue
		}
		hasAvailablePackage = true
		break
	}

	if !hasAvailablePackage {
		s.logger.Warn("所有套餐已用完，触发停机",
			zap.String("carrier_type", carrierType),
			zap.Uint("carrier_id", carrierID))
		return true, nil
	}

	return false, nil
}

// triggerSuspensionAfterCommit 在事务提交后触发停机检查，避免回调读到未提交状态。
func (s *UsageService) triggerSuspensionAfterCommit(ctx context.Context, carrierType string, carrierID uint) {
	if s.stopResumeCallback == nil {
		if carrierType == constants.AssetTypeDevice {
			s.logger.Warn("停复机回调未注入，跳过设备绑定卡停机",
				zap.Uint("device_id", carrierID))
		}
		return
	}

	stopCtx := context.WithoutCancel(ctx)
	if carrierType == constants.AssetTypeIotCard {
		go func() {
			if err := s.stopResumeCallback.CheckAndStopCard(stopCtx, carrierID); err != nil {
				s.logger.Error("调用停机服务失败",
					zap.Uint("card_id", carrierID),
					zap.Error(err))
			}
		}()
		return
	}

	if carrierType != constants.AssetTypeDevice {
		return
	}

	bindings, err := s.deviceSimBindingStore.ListByDeviceID(stopCtx, carrierID)
	if err != nil {
		s.logger.Error("查询设备绑定卡失败",
			zap.Uint("device_id", carrierID),
			zap.Error(err))
		return
	}
	if len(bindings) == 0 {
		s.logger.Debug("设备无绑定卡，跳过停机",
			zap.Uint("device_id", carrierID))
		return
	}

	for _, b := range bindings {
		cardID := b.IotCardID
		go func(cID uint) {
			if err := s.stopResumeCallback.CheckAndStopCard(stopCtx, cID); err != nil {
				s.logger.Error("调用停机服务失败",
					zap.Uint("card_id", cID),
					zap.Error(err))
			}
		}(cardID)
	}
}

// recordOverflowToDepletedPackage 在所有套餐已耗尽时，将溢出流量写入最近的 Depleted 套餐日记录。
// 目的：保持流量详单连续性，即便套餐用完期间上游仍有流量使用，详单不会断档。
// 只写日记录和累加 data_usage_mb，不改变套餐 status，不触发停机。
func (s *UsageService) recordOverflowToDepletedPackage(ctx context.Context, tx *gorm.DB, carrierType string, carrierID uint, overflowMB int64, today string) error {
	query := tx.Where("status = ?", constants.PackageUsageStatusDepleted)
	switch carrierType {
	case constants.AssetTypeIotCard:
		query = query.Where("iot_card_id = ?", carrierID)
	case constants.AssetTypeDevice:
		query = query.Where("device_id = ?", carrierID)
	default:
		return nil
	}

	// 优先选主套餐（master_usage_id IS NULL），主套餐不存在时再退而求其次取加油包
	var pkg model.PackageUsage
	err := query.Where("master_usage_id IS NULL").Order("id DESC").First(&pkg).Error
	if err == gorm.ErrRecordNotFound {
		err = query.Order("id DESC").First(&pkg).Error
	}
	if err != nil {
		// 找不到任何 Depleted 套餐，静默忽略
		return nil
	}

	newUsage := pkg.DataUsageMB + overflowMB
	if err := tx.Model(&pkg).Update("data_usage_mb", newUsage).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "更新溢出流量失败")
	}

	if err := s.updateDailyRecord(ctx, tx, pkg.ID, today, overflowMB, newUsage); err != nil {
		return err
	}

	s.logger.Info("记录溢出流量到已耗尽套餐",
		zap.Uint("package_usage_id", pkg.ID),
		zap.Int64("overflow_mb", overflowMB),
		zap.Int64("new_usage_mb", newUsage))

	return nil
}
