package cardobservation

import (
	"context"
	"strconv"
	"strings"

	"github.com/bytedance/sonic"
	"gorm.io/gorm"

	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"
)

// SeriesEventWriter 将业务成功观测请求写入公共 Outbox。
type SeriesEventWriter struct {
	outbox *outbox.Repository
}

// NewSeriesEventWriter 创建业务观测事件 Writer。
func NewSeriesEventWriter(repository *outbox.Repository) *SeriesEventWriter {
	return &SeriesEventWriter{outbox: repository}
}

// AppendSeriesRequested 在原业务事务中追加稳定观测请求事件。
func (w *SeriesEventWriter) AppendSeriesRequested(ctx context.Context, tx *gorm.DB, event cardapp.SeriesRequestedEvent) error {
	if w == nil || w.outbox == nil {
		return errors.New(errors.CodeInternalError, "业务观测 Outbox Writer 未配置")
	}
	if cardapp.IsSeriesTriggerSuppressed(ctx) {
		return nil
	}
	if strings.TrimSpace(event.EventID) == "" || event.ResourceID == 0 || len(event.SyncTypes) == 0 {
		return errors.New(errors.CodeInvalidParam, "业务观测事件载荷不完整")
	}
	if tx == nil {
		return errors.New(errors.CodeInternalError, "业务观测事件缺少原业务事务")
	}
	if event.ResourceType == constants.CardObservationResourceTypeDevice {
		event.ResourceIDs = nil
		if err := tx.WithContext(ctx).
			Model(&model.DeviceSimBinding{}).
			Where("device_id = ? AND bind_status = ?", event.ResourceID, constants.BindStatusBound).
			Order("slot_position ASC").
			Pluck("iot_card_id", &event.ResourceIDs).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "冻结业务观测设备绑定卡快照失败")
		}
	}
	_, err := w.outbox.AppendIdempotent(ctx, tx, outbox.Envelope{
		EventID: event.EventID, EventType: constants.OutboxEventTypeCardSeriesRequested,
		PayloadVersion: constants.CardSeriesRequestedPayloadVersionV1,
		AggregateType:  event.ResourceType, AggregateID: strconv.FormatUint(uint64(event.ResourceID), 10),
		ResourceType: event.ResourceType, ResourceID: strconv.FormatUint(uint64(event.ResourceID), 10),
		BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID,
		Payload: event,
	})
	if err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "写入业务观测 Outbox 事件失败")
	}
	return nil
}

// SeriesRequestedConsumer 消费业务成功事实并触发卡观测序列。
type SeriesRequestedConsumer struct {
	trigger *cardapp.SeriesTrigger
}

// NewSeriesRequestedConsumer 创建业务观测序列消费者。
func NewSeriesRequestedConsumer(trigger *cardapp.SeriesTrigger) *SeriesRequestedConsumer {
	return &SeriesRequestedConsumer{trigger: trigger}
}

// Consume 将一个业务事件展开为卡观测序列；重复投递由稳定序列键幂等。
func (c *SeriesRequestedConsumer) Consume(ctx context.Context, envelope outbox.DeliveryEnvelope) error {
	if c == nil || c.trigger == nil {
		return errors.New(errors.CodeInternalError, "业务观测序列消费者未配置")
	}
	if envelope.EventType != constants.OutboxEventTypeCardSeriesRequested || envelope.PayloadVersion != constants.CardSeriesRequestedPayloadVersionV1 {
		return errors.New(errors.CodeInvalidParam, "业务观测事件类型或版本不受支持")
	}
	var event cardapp.SeriesRequestedEvent
	if err := sonic.Unmarshal(envelope.Payload, &event); err != nil {
		return errors.Wrap(errors.CodeInvalidParam, err, "业务观测事件载荷无法解析")
	}
	if event.EventID != envelope.EventID || event.ResourceID == 0 || len(event.SyncTypes) == 0 {
		return errors.New(errors.CodeInvalidParam, "业务观测事件载荷不完整")
	}
	if event.ResourceType != constants.CardObservationResourceTypeCard && event.ResourceType != constants.CardObservationResourceTypeDevice {
		return errors.New(errors.CodeInvalidParam, "业务观测事件资源类型不受支持")
	}
	cardIDs := []uint{event.ResourceID}
	if event.ResourceType == constants.CardObservationResourceTypeDevice {
		cardIDs = event.ResourceIDs
	}
	if len(cardIDs) == 0 {
		return nil
	}
	for _, cardID := range cardIDs {
		for _, syncType := range event.SyncTypes {
			request := cardapp.SeriesRequest{
				Scene: event.Scene, ResourceType: constants.CardObservationResourceTypeCard,
				ResourceID: strconv.FormatUint(uint64(cardID), 10), SyncType: syncType,
				ExpectedValue: event.ExpectedValue, Source: event.Source,
				RequestID: event.RequestID, CorrelationID: event.CorrelationID,
			}
			key := event.EventID + ":" + strconv.FormatUint(uint64(cardID), 10) + ":" + syncType
			if _, _, err := c.trigger.TriggerEvent(ctx, key, request); err != nil {
				return err
			}
		}
	}
	return nil
}

var _ cardapp.SeriesEventWriter = (*SeriesEventWriter)(nil)
var _ outbox.EventConsumer = (*SeriesRequestedConsumer)(nil)
