// Package cardobservation 提供卡实名观测的复杂写用例。
package cardobservation

import (
	"context"
	"strconv"
	"time"

	domain "github.com/break/junhong_cmp_fiber/internal/domain/cardobservation"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// RealnameChangedEvent 是卡实名状态变化的可靠领域事实。
type RealnameChangedEvent struct {
	EventID       string    `json:"event_id"`
	CardID        uint      `json:"card_id"`
	BeforeStatus  int       `json:"before_status"`
	AfterStatus   int       `json:"after_status"`
	FirstVerified bool      `json:"first_verified"`
	ObservedAt    time.Time `json:"observed_at"`
	Source        string    `json:"source"`
	Scene         string    `json:"scene"`
	RequestID     string    `json:"request_id,omitempty"`
	CorrelationID string    `json:"correlation_id,omitempty"`
}

// EventWriter 在卡状态事务中追加领域 Outbox 事件。
type EventWriter interface {
	AppendRealname(ctx context.Context, tx *gorm.DB, event RealnameChangedEvent) error
	AppendTraffic(ctx context.Context, tx *gorm.DB, event TrafficIncrementedEvent) error
	AppendNetwork(ctx context.Context, tx *gorm.DB, event NetworkChangedEvent) error
}

// CacheInvalidator 在业务事务提交后失效卡缓存。
type CacheInvalidator interface {
	Invalidate(ctx context.Context, cardID uint)
}

// Service 负责卡实名观测的锁定、规则应用和可靠事件写入。
type Service struct {
	db          *gorm.DB
	eventWriter EventWriter
	cache       CacheInvalidator
}

// NewService 创建卡实名观测应用服务。
func NewService(db *gorm.DB, eventWriter EventWriter, cache CacheInvalidator) *Service {
	return &Service{db: db, eventWriter: eventWriter, cache: cache}
}

// ApplyCardObservation 在同一事务中应用实名状态、逆转窗口和状态变更事件。
func (s *Service) ApplyCardObservation(ctx context.Context, observation domain.RealnameObservation) (domain.RealnameDecision, error) {
	if s == nil || s.db == nil || s.eventWriter == nil {
		return domain.RealnameDecision{}, errors.New(errors.CodeInternalError, "卡实名观测能力未完整配置")
	}
	var decision domain.RealnameDecision
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var card model.IotCard
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", observation.CardID).First(&card).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				return errors.New(errors.CodeNotFound, "IoT卡不存在")
			}
			return errors.Wrap(errors.CodeDatabaseError, err, "锁定IoT卡失败")
		}
		nextDecision, decisionErr := domain.ApplyRealname(domain.CardRealnameSnapshot{
			CardID: card.ID, Status: card.RealNameStatus, FirstRealnameAt: card.FirstRealnameAt,
			ReversalCount: card.RealnameReversalCount, ReversalStartedAt: card.RealnameReversalStartedAt,
		}, observation)
		if decisionErr != nil {
			return decisionErr
		}
		decision = nextDecision
		updates := map[string]any{
			"last_real_name_check_at":      observation.Metadata.ObservedAt,
			"realname_reversal_count":      decision.ReversalCount,
			"realname_reversal_started_at": decision.ReversalStartedAt,
		}
		if observation.Metadata.Source != constants.CardObservationSourceManualOverride {
			updates["last_sync_time"] = observation.Metadata.ObservedAt
		}
		if decision.StatusChanged {
			updates["real_name_status"] = decision.AfterStatus
			updates["activation_status"] = gorm.Expr(`CASE WHEN network_status = ? AND (card_category = ? OR ? = ?) THEN 1 ELSE 0 END`, constants.NetworkStatusOnline, constants.CardCategoryIndustry, decision.AfterStatus, constants.RealNameStatusVerified)
			updates["activated_at"] = gorm.Expr(`CASE WHEN activated_at IS NULL AND (network_status = ? AND (card_category = ? OR ? = ?)) THEN ? ELSE activated_at END`, constants.NetworkStatusOnline, constants.CardCategoryIndustry, decision.AfterStatus, constants.RealNameStatusVerified, observation.Metadata.ObservedAt)
		}
		if decision.FirstVerified {
			updates["first_realname_at"] = observation.Metadata.ObservedAt
		}
		result := tx.Model(&model.IotCard{}).Where("id = ? AND real_name_status = ?", card.ID, card.RealNameStatus).Updates(updates)
		if result.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新卡实名事实失败")
		}
		if result.RowsAffected != 1 {
			return errors.New(errors.CodeConflict, "卡实名状态已被其他请求更新")
		}
		if decision.StatusChanged {
			eventID := "card-realname:" + strconv.FormatUint(uint64(card.ID), 10) + ":" + observation.Metadata.ObservationID + ":changed"
			if err := s.eventWriter.AppendRealname(ctx, tx, RealnameChangedEvent{
				EventID: eventID, CardID: card.ID, BeforeStatus: card.RealNameStatus, AfterStatus: decision.AfterStatus,
				FirstVerified: decision.FirstVerified, ObservedAt: observation.Metadata.ObservedAt,
				Source: observation.Metadata.Source, Scene: observation.Metadata.Scene,
				RequestID: observation.Metadata.RequestID, CorrelationID: observation.Metadata.CorrelationID,
			}); err != nil {
				return errors.Wrap(errors.CodeDatabaseError, err, "写入卡实名 Outbox 事件失败")
			}
		}
		return nil
	})
	if err != nil {
		return domain.RealnameDecision{}, err
	}
	if s.cache != nil {
		s.cache.Invalidate(ctx, observation.CardID)
	}
	return decision, nil
}
