// Package cardobservation 提供卡观测的 Outbox、缓存和消费者适配器。
package cardobservation

import (
	"context"
	"strconv"
	"time"

	"github.com/bytedance/sonic"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	cardapp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
	"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

func uintString(value uint) string {
	return strconv.FormatUint(uint64(value), 10)
}

// EventWriter 将卡实名状态变化写入公共 Outbox。
type EventWriter struct {
	outbox *outbox.Repository
}

// NewEventWriter 创建卡实名事件 Writer。
func NewEventWriter(repository *outbox.Repository) *EventWriter {
	return &EventWriter{outbox: repository}
}

// Append 在调用方事务中追加卡实名状态变化事件。
func (w *EventWriter) AppendRealname(ctx context.Context, tx *gorm.DB, event cardapp.RealnameChangedEvent) error {
	if w == nil || w.outbox == nil {
		return errors.New(errors.CodeInternalError, "卡实名 Outbox Writer 未配置")
	}
	_, err := w.outbox.Append(ctx, tx, outbox.Envelope{
		EventID: event.EventID, EventType: constants.OutboxEventTypeCardRealnameChanged,
		PayloadVersion: constants.CardRealnameChangedPayloadVersionV1,
		AggregateType:  "iot_card", AggregateID: uintString(event.CardID),
		ResourceType: constants.AssetTypeIotCard, ResourceID: uintString(event.CardID),
		BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID,
		Payload: event,
	})
	return err
}

// AppendTraffic 在调用方事务中追加卡流量正增量事件。
func (w *EventWriter) AppendTraffic(ctx context.Context, tx *gorm.DB, event cardapp.TrafficIncrementedEvent) error {
	if w == nil || w.outbox == nil {
		return errors.New(errors.CodeInternalError, "卡流量 Outbox Writer 未配置")
	}
	_, err := w.outbox.Append(ctx, tx, outbox.Envelope{
		EventID: event.EventID, EventType: constants.OutboxEventTypeCardTrafficIncremented,
		PayloadVersion: constants.CardTrafficIncrementedPayloadVersionV1,
		AggregateType:  "iot_card", AggregateID: uintString(event.CardID),
		ResourceType: constants.AssetTypeIotCard, ResourceID: uintString(event.CardID),
		BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID,
		Payload: event,
	})
	return err
}

// AppendNetwork 在调用方事务中追加卡网络状态变化事件。
func (w *EventWriter) AppendNetwork(ctx context.Context, tx *gorm.DB, event cardapp.NetworkChangedEvent) error {
	if w == nil || w.outbox == nil {
		return errors.New(errors.CodeInternalError, "卡网络 Outbox Writer 未配置")
	}
	_, err := w.outbox.Append(ctx, tx, outbox.Envelope{
		EventID: event.EventID, EventType: constants.OutboxEventTypeCardNetworkChanged,
		PayloadVersion: constants.CardNetworkChangedPayloadVersionV1,
		AggregateType:  "iot_card", AggregateID: uintString(event.CardID),
		ResourceType: constants.AssetTypeIotCard, ResourceID: uintString(event.CardID),
		BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID,
		Payload: event,
	})
	return err
}

// CacheInvalidator 在提交后删除轮询卡缓存和旧 Redis 逆转计数。
type CacheInvalidator struct {
	redis  *redis.Client
	logger *zap.Logger
}

// NewCacheInvalidator 创建卡观测缓存失效适配器。
func NewCacheInvalidator(redisClient *redis.Client, logger *zap.Logger) *CacheInvalidator {
	return &CacheInvalidator{redis: redisClient, logger: logger}
}

// Invalidate 删除可由数据库重建的缓存；失败只告警，不伪造事务回滚。
func (i *CacheInvalidator) Invalidate(ctx context.Context, cardID uint) {
	if i == nil || i.redis == nil {
		return
	}
	if err := i.redis.Del(ctx, constants.RedisPollingCardInfoKey(cardID), constants.RedisPollingRealnameReversalCountKey(cardID)).Err(); err != nil && i.logger != nil {
		i.logger.Warn("卡实名事实已提交但缓存失效失败", zap.Uint("card_id", cardID), zap.Error(err))
	}
}

// RealnameActivator 激活卡或设备待实名套餐。
type RealnameActivator interface {
	ActivateByRealname(ctx context.Context, carrierType string, carrierID uint) error
}

// TrafficDeductor 按卡流量正增量扣减套餐流量。
type TrafficDeductor interface {
	DeductDataUsage(ctx context.Context, carrierType string, carrierID uint, usageMB float64) error
}

// StopResumeEvaluator 根据卡的当前事实执行停复机评估。
type StopResumeEvaluator interface {
	EvaluateAndAct(ctx context.Context, card *model.IotCard) error
}

// DeviceBindingReader 查询卡当前绑定的设备。
type DeviceBindingReader interface {
	GetActiveBindingByCardID(ctx context.Context, cardID uint) (*model.DeviceSimBinding, error)
}

// RealnameChangedConsumer 消费实名变化事件并串联现有幂等业务副作用。
type RealnameChangedConsumer struct {
	db        *gorm.DB
	activator RealnameActivator
	binding   DeviceBindingReader
	evaluator StopResumeEvaluator
}

// NetworkChangedConsumer 消费网络状态变化并按当前权威事实执行停复机评估。
type NetworkChangedConsumer struct {
	db        *gorm.DB
	evaluator StopResumeEvaluator
}

// NewNetworkChangedConsumer 创建卡网络状态变化事件消费者。
func NewNetworkChangedConsumer(db *gorm.DB, evaluator StopResumeEvaluator) *NetworkChangedConsumer {
	return &NetworkChangedConsumer{db: db, evaluator: evaluator}
}

// Consume 校验网络事件并按当前卡事实执行评估。
func (c *NetworkChangedConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error {
	if c == nil || c.db == nil {
		return errors.New(errors.CodeInternalError, "卡网络事件消费者未配置")
	}
	ctx = cardapp.SuppressSeriesTriggerContext(ctx)
	if envelope.EventType != constants.OutboxEventTypeCardNetworkChanged || envelope.PayloadVersion != constants.CardNetworkChangedPayloadVersionV1 {
		return errors.New(errors.CodeInvalidParam, "卡网络事件类型或版本不受支持")
	}
	var event cardapp.NetworkChangedEvent
	if err := sonic.Unmarshal(envelope.Payload, &event); err != nil {
		return errors.Wrap(errors.CodeInvalidParam, err, "卡网络事件载荷无法解析")
	}
	if event.EventID != envelope.EventID || event.CardID == 0 || event.BeforeStatus == event.AfterStatus {
		return errors.New(errors.CodeInvalidParam, "卡网络事件载荷不完整")
	}
	if c.evaluator == nil {
		return nil
	}
	var card model.IotCard
	if err := c.db.WithContext(ctx).Where("id = ?", event.CardID).First(&card).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil
		}
		return errors.Wrap(errors.CodeDatabaseError, err, "查询网络变化后的卡事实失败")
	}
	return c.evaluator.EvaluateAndAct(ctx, &card)
}

// NewRealnameChangedConsumer 创建卡实名变化事件消费者。
func NewRealnameChangedConsumer(db *gorm.DB, activator RealnameActivator, binding DeviceBindingReader, evaluator StopResumeEvaluator) *RealnameChangedConsumer {
	return &RealnameChangedConsumer{db: db, activator: activator, binding: binding, evaluator: evaluator}
}

// Consume 校验载荷，并以当前权威卡事实执行可重入副作用。
func (c *RealnameChangedConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error {
	if c == nil || c.db == nil {
		return errors.New(errors.CodeInternalError, "卡实名事件消费者未配置")
	}
	ctx = cardapp.SuppressSeriesTriggerContext(ctx)
	if envelope.EventType != constants.OutboxEventTypeCardRealnameChanged || envelope.PayloadVersion != constants.CardRealnameChangedPayloadVersionV1 {
		return errors.New(errors.CodeInvalidParam, "卡实名事件类型或版本不受支持")
	}
	var event cardapp.RealnameChangedEvent
	if err := sonic.Unmarshal(envelope.Payload, &event); err != nil {
		return errors.Wrap(errors.CodeInvalidParam, err, "卡实名事件载荷无法解析")
	}
	if event.EventID != envelope.EventID || event.CardID == 0 || event.BeforeStatus == event.AfterStatus {
		return errors.New(errors.CodeInvalidParam, "卡实名事件载荷不完整")
	}
	if event.FirstVerified && c.activator != nil {
		if err := c.activator.ActivateByRealname(ctx, constants.AssetTypeIotCard, event.CardID); err != nil {
			return err
		}
		if c.binding != nil {
			binding, err := c.binding.GetActiveBindingByCardID(ctx, event.CardID)
			if err == nil && binding != nil {
				if err := c.activator.ActivateByRealname(ctx, constants.AssetTypeDevice, binding.DeviceID); err != nil {
					return err
				}
			} else if err != nil && err != gorm.ErrRecordNotFound {
				return errors.Wrap(errors.CodeDatabaseError, err, "查询实名卡绑定设备失败")
			}
		}
	}
	if c.evaluator == nil {
		return nil
	}
	var card model.IotCard
	if err := c.db.WithContext(ctx).Where("id = ?", event.CardID).First(&card).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil
		}
		return errors.Wrap(errors.CodeDatabaseError, err, "查询实名变化后的卡事实失败")
	}
	return c.evaluator.EvaluateAndAct(ctx, &card)
}

// TrafficIncrementedConsumer 消费卡流量正增量并推进幂等副作用进度。
type TrafficIncrementedConsumer struct {
	db        *gorm.DB
	redis     *redis.Client
	deductor  TrafficDeductor
	evaluator StopResumeEvaluator
}

// NewTrafficIncrementedConsumer 创建卡流量正增量事件消费者。
func NewTrafficIncrementedConsumer(db *gorm.DB, redisClient *redis.Client, deductor TrafficDeductor, evaluator StopResumeEvaluator) *TrafficIncrementedConsumer {
	return &TrafficIncrementedConsumer{db: db, redis: redisClient, deductor: deductor, evaluator: evaluator}
}

// Consume 按事件处理进度避免至少一次投递重复扣减套餐流量。
func (c *TrafficIncrementedConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error {
	if c == nil || c.db == nil || c.redis == nil || c.deductor == nil {
		return errors.New(errors.CodeInternalError, "卡流量事件消费者未配置")
	}
	ctx = cardapp.SuppressSeriesTriggerContext(ctx)
	if envelope.EventType != constants.OutboxEventTypeCardTrafficIncremented || envelope.PayloadVersion != constants.CardTrafficIncrementedPayloadVersionV1 {
		return errors.New(errors.CodeInvalidParam, "卡流量事件类型或版本不受支持")
	}
	var event cardapp.TrafficIncrementedEvent
	if err := sonic.Unmarshal(envelope.Payload, &event); err != nil {
		return errors.Wrap(errors.CodeInvalidParam, err, "卡流量事件载荷无法解析")
	}
	if event.EventID != envelope.EventID || event.CardID == 0 || event.IncrementMB <= 0 {
		return errors.New(errors.CodeInvalidParam, "卡流量事件载荷不完整")
	}
	receipt, err := c.acquireTrafficEffect(ctx, event.EventID)
	if err != nil {
		return err
	}
	if receipt.Status == constants.CardObservationEffectStatusCompleted {
		return nil
	}
	if receipt.Status == constants.CardObservationEffectStatusProcessing {
		dailyKey := constants.RedisCardDailyTrafficKey(event.CardID, event.ObservedAt.Format("2006-01-02"))
		pipe := c.redis.TxPipeline()
		pipe.IncrByFloat(ctx, dailyKey, event.IncrementMB)
		pipe.Expire(ctx, dailyKey, 48*time.Hour)
		if _, err := pipe.Exec(ctx); err != nil {
			return errors.Wrap(errors.CodeInternalError, err, "记录卡日流量缓冲失败，处理结果未知")
		}
		if err := c.updateTrafficEffectStatus(ctx, receipt.ID, constants.CardObservationEffectStatusProcessing, constants.CardObservationEffectStatusDailySaved); err != nil {
			return err
		}
		receipt.Status = constants.CardObservationEffectStatusDailySaved
	}
	if receipt.Status == constants.CardObservationEffectStatusDailySaved {
		if err := c.updateTrafficEffectStatus(ctx, receipt.ID, constants.CardObservationEffectStatusDailySaved, constants.CardObservationEffectStatusProcessing); err != nil {
			return err
		}
		if err := c.deductor.DeductDataUsage(ctx, constants.AssetTypeIotCard, event.CardID, event.IncrementMB); err != nil {
			return err
		}
		if err := c.updateTrafficEffectStatus(ctx, receipt.ID, constants.CardObservationEffectStatusProcessing, constants.CardObservationEffectStatusDeducted); err != nil {
			return err
		}
		receipt.Status = constants.CardObservationEffectStatusDeducted
	}
	if c.evaluator != nil {
		var card model.IotCard
		if err := c.db.WithContext(ctx).Where("id = ?", event.CardID).First(&card).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "查询流量变化后的卡事实失败")
		}
		if err := c.evaluator.EvaluateAndAct(ctx, &card); err != nil {
			return err
		}
	}
	if err := c.updateTrafficEffectStatus(ctx, receipt.ID, constants.CardObservationEffectStatusDeducted, constants.CardObservationEffectStatusCompleted); err != nil {
		return err
	}
	return nil
}

func (c *TrafficIncrementedConsumer) acquireTrafficEffect(ctx context.Context, eventID string) (*model.CardObservationEffect, error) {
	var receipt model.CardObservationEffect
	err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("event_id = ?", eventID).First(&receipt).Error
		if err == gorm.ErrRecordNotFound {
			receipt = model.CardObservationEffect{
				EventID: eventID, EffectType: constants.CardObservationEffectTypeTrafficIncrement,
				Status: constants.CardObservationEffectStatusProcessing,
			}
			return tx.Create(&receipt).Error
		}
		if err != nil {
			return err
		}
		switch receipt.Status {
		case constants.CardObservationEffectStatusCompleted, constants.CardObservationEffectStatusDailySaved, constants.CardObservationEffectStatusDeducted:
			return nil
		case constants.CardObservationEffectStatusProcessing:
			return errors.New(errors.CodeConflict, "卡流量副作用仍在处理或结果未知")
		default:
			return tx.Model(&receipt).Where("status = ?", constants.CardObservationEffectStatusPending).
				Update("status", constants.CardObservationEffectStatusProcessing).Error
		}
	})
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "获取卡流量副作用处理权失败")
	}
	return &receipt, nil
}

func (c *TrafficIncrementedConsumer) updateTrafficEffectStatus(ctx context.Context, id uint, beforeStatus, afterStatus int) error {
	result := c.db.WithContext(ctx).Model(&model.CardObservationEffect{}).
		Where("id = ? AND status = ?", id, beforeStatus).
		Update("status", afterStatus)
	if result.Error != nil {
		return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新卡流量副作用进度失败")
	}
	if result.RowsAffected != 1 {
		return errors.New(errors.CodeConflict, "卡流量副作用进度已被其他消费者更新")
	}
	return nil
}
