package approval

import (
	"context"
	"strconv"
	"time"

	"gorm.io/gorm"

	approvaldomain "github.com/break/junhong_cmp_fiber/internal/domain/approval"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// RepositoryProvider 为当前 GORM 事务提供纯领域 Repository。
type RepositoryProvider interface {
	ForDB(db *gorm.DB) approvaldomain.Repository
}

// TerminalDecisionEvent 是业务消费者接收的渠道无关标准决策事件。
type TerminalDecisionEvent struct {
	EventID            string    `json:"event_id"`
	InstanceID         uint      `json:"instance_id"`
	BusinessType       string    `json:"business_type"`
	BusinessID         uint      `json:"business_id"`
	SubmitterAccountID uint      `json:"submitter_account_id"`
	Decision           string    `json:"decision"`
	Source             string    `json:"source"`
	CorrelationID      string    `json:"correlation_id"`
	OccurredAt         time.Time `json:"occurred_at"`
}

// TerminalEventWriter 在审批状态事务中追加标准决策 Outbox。
type TerminalEventWriter interface {
	Append(ctx context.Context, tx *gorm.DB, event TerminalDecisionEvent) error
}

// DecisionDeliveryWriter 在审批状态事务中创建业务消费租约事实。
type DecisionDeliveryWriter interface {
	Create(ctx context.Context, tx *gorm.DB, event TerminalDecisionEvent) error
}

// SyncDecisionCommand 是回调、兜底轮询和受控人工同步共用的标准决策命令。
type SyncDecisionCommand struct {
	InstanceID       uint
	Decision         string
	DecisionSnapshot []byte
	Source           string
}

// SyncDecisionResult 返回本次是否首次记录该标准终态。
type SyncDecisionResult struct {
	Status        int
	FirstTerminal bool
}

// SyncDecisionService 统一处理各审批渠道回传的标准决策。
type SyncDecisionService struct {
	db             *gorm.DB
	repositories   RepositoryProvider
	eventWriter    TerminalEventWriter
	deliveryWriter DecisionDeliveryWriter
	now            func() time.Time
}

// NewSyncDecisionService 创建标准决策同步用例。
func NewSyncDecisionService(
	db *gorm.DB,
	repositories RepositoryProvider,
	eventWriter TerminalEventWriter,
	deliveryWriter DecisionDeliveryWriter,
	now func() time.Time,
) *SyncDecisionService {
	if now == nil {
		now = time.Now
	}
	return &SyncDecisionService{
		db: db, repositories: repositories, eventWriter: eventWriter, deliveryWriter: deliveryWriter, now: now,
	}
}

// Execute 将回调或轮询取得的权威渠道状态原子转换为通用审批终态和可靠业务事件。
func (s *SyncDecisionService) Execute(ctx context.Context, command SyncDecisionCommand) (*SyncDecisionResult, error) {
	if s == nil || s.db == nil || s.repositories == nil || s.eventWriter == nil || s.deliveryWriter == nil {
		return nil, errors.New(errors.CodeInternalError, "通用审批决策同步用例未完整配置")
	}
	if command.InstanceID == 0 || !isSupportedSyncSource(command.Source) {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	result := &SyncDecisionResult{}
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		repository := s.repositories.ForDB(tx)
		if repository == nil {
			return errors.New(errors.CodeInternalError, "通用审批 Repository 未配置")
		}
		instance, err := repository.GetForUpdate(ctx, command.InstanceID)
		if err != nil {
			return err
		}
		expectedStatus, expectedVersion := instance.Status, instance.Version
		changed, err := instance.ApplyDecision(command.Decision, command.DecisionSnapshot, s.now().UTC())
		if err != nil {
			return err
		}
		result.Status = instance.Status
		if !changed {
			return nil
		}
		saved, err := repository.SaveDecision(ctx, instance, expectedStatus, expectedVersion)
		if err != nil {
			return err
		}
		if !saved {
			return errors.New(errors.CodeConflict, "审批状态已被其他同步任务更新")
		}
		event := TerminalDecisionEvent{
			EventID:    terminalDecisionEventID(instance.ID, command.Decision),
			InstanceID: instance.ID, BusinessType: instance.BusinessType, BusinessID: instance.BusinessID,
			SubmitterAccountID: instance.SubmitterAccountID, Decision: command.Decision, Source: command.Source,
			CorrelationID: instance.CorrelationID, OccurredAt: instance.StatusChangedAt,
		}
		if err := s.deliveryWriter.Create(ctx, tx, event); err != nil {
			return err
		}
		if err := s.eventWriter.Append(ctx, tx, event); err != nil {
			return err
		}
		result.FirstTerminal = true
		return nil
	})
	if err != nil {
		return nil, err
	}
	return result, nil
}

func terminalDecisionEventID(instanceID uint, decision string) string {
	return "approval:" + strconv.FormatUint(uint64(instanceID), 10) + ":" + decision
}

func isSupportedSyncSource(source string) bool {
	return source == constants.ApprovalSyncSourceCallback ||
		source == constants.ApprovalSyncSourcePolling ||
		source == constants.ApprovalSyncSourceManual
}
