package cardobservation

import (
	"strings"
	"time"

	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

const (
	realnameReversalThreshold = 3
	realnameReversalWindow    = 10 * time.Minute
)

// ObservationMetadata 描述一次上游实名观测的可追踪元数据。
type ObservationMetadata struct {
	ObservationID   string
	Source          string
	Scene           string
	ObservedAt      time.Time
	RequestID       string
	CorrelationID   string
	UpstreamSummary string
}

// RealnameObservation 是领域层接收的标准实名观测。
type RealnameObservation struct {
	CardID   uint
	Verified bool
	Metadata ObservationMetadata
}

// CardRealnameSnapshot 是应用层从持久化模型映射出的最小卡实名快照。
type CardRealnameSnapshot struct {
	CardID            uint
	Status            int
	FirstRealnameAt   *time.Time
	ReversalCount     int
	ReversalStartedAt *time.Time
}

// RealnameDecision 描述应用本次应持久化的实名事实。
type RealnameDecision struct {
	StatusChanged     bool
	FirstVerified     bool
	ReversalPending   bool
	ReversalCount     int
	ReversalStartedAt *time.Time
	ReversalReset     bool
	BeforeStatus      int
	AfterStatus       int
}

// ApplyRealname 根据观测来源应用首次实名和周期逆转规则。
func ApplyRealname(snapshot CardRealnameSnapshot, observation RealnameObservation) (RealnameDecision, error) {
	if err := validateObservation(observation); err != nil {
		return RealnameDecision{}, err
	}
	decision := RealnameDecision{
		BeforeStatus: snapshot.Status, AfterStatus: snapshot.Status,
		ReversalCount: snapshot.ReversalCount, ReversalStartedAt: snapshot.ReversalStartedAt,
	}
	if snapshot.Status != constants.RealNameStatusNotVerified && snapshot.Status != constants.RealNameStatusVerified {
		return RealnameDecision{}, errors.New(errors.CodeInvalidStatus, "卡实名状态无效")
	}

	if observation.Verified {
		decision.AfterStatus = constants.RealNameStatusVerified
		decision.StatusChanged = snapshot.Status != decision.AfterStatus
		decision.FirstVerified = decision.StatusChanged && snapshot.FirstRealnameAt == nil
		decision.ReversalReset = snapshot.ReversalCount != 0 || snapshot.ReversalStartedAt != nil
		decision.ReversalCount = 0
		decision.ReversalStartedAt = nil
		return decision, nil
	}

	if snapshot.Status == constants.RealNameStatusNotVerified {
		decision.ReversalReset = snapshot.ReversalCount != 0 || snapshot.ReversalStartedAt != nil
		decision.ReversalCount = 0
		decision.ReversalStartedAt = nil
		return decision, nil
	}
	if observation.Metadata.Source == constants.CardObservationSourceCarrierCallback {
		// 解除实名回调只留痕，不把外部单次结果变成本地逆转事实。
		return decision, nil
	}
	if observation.Metadata.Source == constants.CardObservationSourceManualOverride {
		decision.AfterStatus = constants.RealNameStatusNotVerified
		decision.StatusChanged = true
		decision.ReversalReset = true
		decision.ReversalCount = 0
		decision.ReversalStartedAt = nil
		return decision, nil
	}

	count := snapshot.ReversalCount
	startedAt := snapshot.ReversalStartedAt
	now := observation.Metadata.ObservedAt
	if startedAt == nil || now.Before(*startedAt) || now.Sub(*startedAt) > realnameReversalWindow {
		count = 0
		startedAt = &now
	}
	count++
	decision.ReversalCount = count
	decision.ReversalStartedAt = startedAt
	if count < realnameReversalThreshold {
		decision.ReversalPending = true
		return decision, nil
	}
	decision.AfterStatus = constants.RealNameStatusNotVerified
	decision.StatusChanged = true
	decision.ReversalReset = true
	decision.ReversalCount = 0
	decision.ReversalStartedAt = nil
	return decision, nil
}

func validateObservation(observation RealnameObservation) error {
	if observation.CardID == 0 || strings.TrimSpace(observation.Metadata.ObservationID) == "" ||
		strings.TrimSpace(observation.Metadata.Source) == "" || strings.TrimSpace(observation.Metadata.Scene) == "" ||
		observation.Metadata.ObservedAt.IsZero() {
		return errors.New(errors.CodeInvalidParam, "实名观测关键字段缺失")
	}
	switch observation.Metadata.Source {
	case constants.CardObservationSourcePolling, constants.CardObservationSourceManualSync,
		constants.CardObservationSourceManualOverride, constants.CardObservationSourceCarrierCallback,
		constants.CardObservationSourceBusinessEvent:
		return nil
	default:
		return errors.New(errors.CodeInvalidParam, "实名观测来源无效")
	}
}
