package task

import (
	"context"
	"strconv"
	"time"

	"go.uber.org/zap"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
)

// getCardWithCache 优先从 Redis 缓存获取卡信息，缓存 miss 时查 DB 并异步写缓存
func (b *PollingBase) getCardWithCache(ctx context.Context, cardID uint) (*model.IotCard, error) {
	key := constants.RedisPollingCardInfoKey(cardID)

	result, err := b.redis.HGetAll(ctx, key).Result()
	if err == nil && len(result) > 0 {
		return parseCardFromCache(cardID, result), nil
	}

	card, err := b.iotCardStore.GetByID(ctx, cardID)
	if err != nil {
		return nil, err
	}

	// 同步写缓存，避免与后续 updateCardCache 产生竞态：
	// 若异步协程在 updateCardCache 之后才执行，会将 last_gateway_reading_mb
	// 覆盖回 DB 旧值（0），导致下次轮询增量重复计全量。
	writeCardToCache(b, key, card)
	return card, nil
}

// updateCardCache 更新 Redis 卡信息缓存中的指定字段
func (b *PollingBase) updateCardCache(ctx context.Context, cardID uint, updates map[string]any) {
	key := constants.RedisPollingCardInfoKey(cardID)
	args := make([]any, 0, len(updates)*2)
	for k, v := range updates {
		args = append(args, k, v)
	}
	if len(args) > 0 {
		if err := b.redis.HSet(ctx, key, args...).Err(); err != nil {
			b.logger.Warn("更新卡缓存失败", zap.Uint("card_id", cardID), zap.Error(err))
		}
	}
}

// invalidateCardCache 删除轮询卡缓存，确保后续从数据库重建
func (b *PollingBase) invalidateCardCache(ctx context.Context, cardID uint) {
	key := constants.RedisPollingCardInfoKey(cardID)
	if err := b.redis.Del(ctx, key).Err(); err != nil {
		b.logger.Warn("删除卡缓存失败", zap.Uint("card_id", cardID), zap.Error(err))
	}
}

// parseCardFromCache 从 Redis Hash 反序列化卡信息
func parseCardFromCache(cardID uint, result map[string]string) *model.IotCard {
	card := &model.IotCard{}
	card.ID = cardID
	if v, ok := result["iccid"]; ok {
		card.ICCID = v
	}
	if v, ok := result["card_category"]; ok {
		card.CardCategory = v
	}
	if v, ok := result["real_name_status"]; ok {
		if status, err := strconv.Atoi(v); err == nil {
			card.RealNameStatus = status
		}
	}
	if v, ok := result["network_status"]; ok {
		if status, err := strconv.Atoi(v); err == nil {
			card.NetworkStatus = status
		}
	}
	if v, ok := result["gateway_extend"]; ok {
		card.GatewayExtend = v
	}
	if v, ok := result["carrier_id"]; ok {
		if id, err := strconv.ParseUint(v, 10, 64); err == nil {
			card.CarrierID = uint(id)
		}
	}
	if v, ok := result["current_month_usage_mb"]; ok {
		if usage, err := strconv.ParseFloat(v, 64); err == nil {
			card.CurrentMonthUsageMB = usage
		}
	}
	if v, ok := result["last_gateway_reading_mb"]; ok {
		if reading, err := strconv.ParseFloat(v, 64); err == nil {
			card.LastGatewayReadingMB = reading
		}
	}
	if v, ok := result["data_usage_mb"]; ok {
		if usage, err := strconv.ParseInt(v, 10, 64); err == nil {
			card.DataUsageMB = usage
		}
	}
	if v, ok := result["stop_reason"]; ok {
		card.StopReason = v
	}
	if v, ok := result["is_standalone"]; ok {
		card.IsStandalone = v == "1" || v == "true"
	} else {
		card.IsStandalone = true
	}
	if v, ok := result["series_id"]; ok {
		if id, err := strconv.ParseUint(v, 10, 64); err == nil {
			seriesID := uint(id)
			card.SeriesID = &seriesID
		}
	}
	if v, ok := result["current_month_start_date"]; ok {
		if ts, err := strconv.ParseInt(v, 10, 64); err == nil {
			t := time.Unix(ts, 0)
			card.CurrentMonthStartDate = &t
		}
	}
	return card
}

// writeCardToCache 将卡信息异步写入 Redis 缓存（7 天 TTL）
func writeCardToCache(b *PollingBase, key string, card *model.IotCard) {
	cacheCtx := context.Background()
	cacheData := map[string]any{
		"id":                      card.ID,
		"iccid":                   card.ICCID,
		"card_category":           card.CardCategory,
		"real_name_status":        card.RealNameStatus,
		"network_status":          card.NetworkStatus,
		"gateway_extend":          card.GatewayExtend,
		"carrier_id":              card.CarrierID,
		"current_month_usage_mb":  card.CurrentMonthUsageMB,
		"last_gateway_reading_mb": card.LastGatewayReadingMB,
		"data_usage_mb":           card.DataUsageMB,
		"stop_reason":             card.StopReason,
		"is_standalone":           boolToStr(card.IsStandalone),
		"cached_at":               time.Now().Unix(),
	}
	if card.SeriesID != nil {
		cacheData["series_id"] = *card.SeriesID
	}
	if card.CurrentMonthStartDate != nil {
		cacheData["current_month_start_date"] = card.CurrentMonthStartDate.Unix()
	}
	pipe := b.redis.Pipeline()
	pipe.HSet(cacheCtx, key, cacheData)
	pipe.Expire(cacheCtx, key, 7*24*time.Hour)
	_, _ = pipe.Exec(cacheCtx)
}
