// Package approval 提供渠道无关的通用审批领域事实。
package approval

import (
	"strings"
	"time"

	"github.com/bytedance/sonic"

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

// Instance 是只保存渠道无关事实的通用审批实例。
type Instance struct {
	ID                 uint
	BusinessType       string
	BusinessID         uint
	SubmitterAccountID uint
	SubmitterSnapshot  []byte
	Provider           string
	ExternalRef        string
	Status             int
	RequestSnapshot    []byte
	DecisionSnapshot   []byte
	CorrelationID      string
	Version            int
	StatusChangedAt    time.Time
	CreatedAt          time.Time
	UpdatedAt          time.Time
}

// NewInstanceParams 是创建通用审批实例所需的稳定业务事实。
type NewInstanceParams struct {
	BusinessType       string
	BusinessID         uint
	SubmitterAccountID uint
	SubmitterSnapshot  []byte
	Provider           string
	RequestSnapshot    []byte
	CorrelationID      string
}

// NewInstance 创建处于提交中的渠道无关审批实例。
func NewInstance(params NewInstanceParams, now time.Time) (*Instance, error) {
	businessType := strings.TrimSpace(params.BusinessType)
	provider := strings.TrimSpace(params.Provider)
	correlationID := strings.TrimSpace(params.CorrelationID)
	if businessType == "" || params.BusinessID == 0 || params.SubmitterAccountID == 0 || provider == "" || correlationID == "" {
		return nil, errors.New(errors.CodeInvalidParam, "通用审批实例的业务引用、提交人、渠道和关联 ID 不能为空")
	}
	if !isJSONObject(params.SubmitterSnapshot) || !isJSONObject(params.RequestSnapshot) {
		return nil, errors.New(errors.CodeInvalidParam, "通用审批实例快照必须是有效 JSON 对象")
	}
	if now.IsZero() {
		return nil, errors.New(errors.CodeInvalidParam, "通用审批实例创建时间不能为空")
	}
	now = now.UTC()
	return &Instance{
		BusinessType: businessType, BusinessID: params.BusinessID,
		SubmitterAccountID: params.SubmitterAccountID, SubmitterSnapshot: cloneBytes(params.SubmitterSnapshot),
		Provider: provider, Status: constants.ApprovalStatusSubmitting,
		RequestSnapshot: cloneBytes(params.RequestSnapshot), CorrelationID: correlationID,
		Version: constants.ApprovalInitialVersion, StatusChangedAt: now, CreatedAt: now, UpdatedAt: now,
	}, nil
}

// StatusForDecision 将渠道 Adapter 输出的标准决策映射为通用审批终态。
func StatusForDecision(decision string) (int, error) {
	switch decision {
	case constants.ApprovalDecisionApproved:
		return constants.ApprovalStatusApproved, nil
	case constants.ApprovalDecisionRejected:
		return constants.ApprovalStatusRejected, nil
	case constants.ApprovalDecisionCancelled:
		return constants.ApprovalStatusCancelled, nil
	case constants.ApprovalDecisionDeleted:
		return constants.ApprovalStatusDeleted, nil
	case constants.ApprovalDecisionRevokedAfterApproved:
		return constants.ApprovalStatusRevokedAfterApproved, nil
	default:
		return 0, errors.New(errors.CodeInvalidParam, "审批渠道返回了不受支持的标准决策")
	}
}

// IsTerminalStatus 判断状态是否为可分发给业务消费者的标准终态。
func IsTerminalStatus(status int) bool {
	return status == constants.ApprovalStatusApproved ||
		status == constants.ApprovalStatusRejected ||
		status == constants.ApprovalStatusCancelled ||
		status == constants.ApprovalStatusDeleted ||
		status == constants.ApprovalStatusRevokedAfterApproved
}

// ApplyDecision 校验标准决策状态迁移并冻结首次到达该终态的决策快照。
func (i *Instance) ApplyDecision(decision string, snapshot []byte, now time.Time) (bool, error) {
	if i == nil || now.IsZero() || !isJSONObject(snapshot) {
		return false, errors.New(errors.CodeInvalidParam, "审批决策实例、快照和决策时间不能为空")
	}
	targetStatus, err := StatusForDecision(decision)
	if err != nil {
		return false, err
	}
	if i.Status == targetStatus {
		return false, nil
	}
	if targetStatus == constants.ApprovalStatusRevokedAfterApproved {
		if i.Status != constants.ApprovalStatusApproved {
			return false, errors.New(errors.CodeInvalidStatus, "只有已通过审批可以进入通过后撤销状态")
		}
	} else if IsTerminalStatus(i.Status) {
		return false, errors.New(errors.CodeInvalidStatus, "审批已进入其他标准终态")
	}
	now = now.UTC()
	i.Status = targetStatus
	i.DecisionSnapshot = cloneBytes(snapshot)
	i.StatusChangedAt = now
	i.UpdatedAt = now
	i.Version++
	return true, nil
}

func isJSONObject(value []byte) bool {
	var object map[string]any
	return len(value) > 0 && sonic.Unmarshal(value, &object) == nil && object != nil
}

func cloneBytes(value []byte) []byte {
	cloned := make([]byte, len(value))
	copy(cloned, value)
	return cloned
}
