package commerce

import (
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

var ErrAutoRenewalAlreadyProcessed = errors.New("auto renewal already processed")

func (s *Service) ListInstances(ctx context.Context, userID int64) ([]InstanceResponse, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var instances []Instance
	if err := s.db.WithContext(ctx).
		Where("user_id = ?", userID).
		Scopes(publicVisibleInstanceScope).
		Order("id DESC").
		Find(&instances).Error; err != nil {
		return nil, err
	}
	products, groups, err := s.productsAndGroupsForInstances(ctx, instances)
	if err != nil {
		return nil, err
	}
	out := make([]InstanceResponse, 0, len(instances))
	for _, instance := range instances {
		product := products[instance.ProductID]
		var group *ProductGroup
		if product != nil {
			group = groups[product.GroupID]
		}
		out = append(out, s.newInstanceResponse(instance, product, group, nil, false))
	}
	return out, nil
}

func (s *Service) productsAndGroupsForInstances(ctx context.Context, instances []Instance) (map[int64]*Product, map[int64]*ProductGroup, error) {
	productsByID := map[int64]*Product{}
	groupsByID := map[int64]*ProductGroup{}
	if len(instances) == 0 {
		return productsByID, groupsByID, nil
	}
	productIDs := make([]int64, 0, len(instances))
	seenProducts := map[int64]struct{}{}
	for _, instance := range instances {
		if _, ok := seenProducts[instance.ProductID]; ok {
			continue
		}
		seenProducts[instance.ProductID] = struct{}{}
		productIDs = append(productIDs, instance.ProductID)
	}
	var products []Product
	if err := s.db.WithContext(ctx).Where("id IN ?", productIDs).Find(&products).Error; err != nil {
		return nil, nil, err
	}
	groupIDs := make([]int64, 0, len(products))
	seenGroups := map[int64]struct{}{}
	for i := range products {
		product := &products[i]
		productsByID[product.ID] = product
		if _, ok := seenGroups[product.GroupID]; ok {
			continue
		}
		seenGroups[product.GroupID] = struct{}{}
		groupIDs = append(groupIDs, product.GroupID)
	}
	if len(groupIDs) == 0 {
		return productsByID, groupsByID, nil
	}
	var groups []ProductGroup
	if err := s.db.WithContext(ctx).Where("id IN ?", groupIDs).Find(&groups).Error; err != nil {
		return nil, nil, err
	}
	for i := range groups {
		group := &groups[i]
		groupsByID[group.ID] = group
	}
	return productsByID, groupsByID, nil
}

func (s *Service) GetInstance(ctx context.Context, userID int64, instanceID int64) (InstanceResponse, error) {
	if err := s.requireDB(); err != nil {
		return InstanceResponse{}, err
	}
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceResponse{}, err
	}
	var syncResult *instanceUpstreamSyncResult
	var syncError *string
	if instance.UpstreamID != nil && instance.CloudServerID != nil && instance.Status != "terminated" {
		syncCtx, cancel := context.WithTimeout(ctx, 8*time.Second)
		result, syncErr := s.syncInstanceUpstream(syncCtx, instance)
		cancel()
		if syncErr != nil {
			message := syncErr.Error()
			syncError = &message
			if s.logger != nil {
				s.logger.Warn("instance detail upstream sync failed", "instance_id", instance.ID, "error", syncErr)
			}
		} else {
			syncResult = &result
			instance = result.Instance
		}
	}
	product, group, err := s.productAndGroup(ctx, instance.ProductID)
	if err != nil {
		return InstanceResponse{}, err
	}
	var rewards []PointReward
	if err := s.db.WithContext(ctx).
		Where("user_id = ? AND instance_id = ? AND status IN ?", userID, instanceID, []string{"pending", "claimable"}).
		Order("id ASC").
		Find(&rewards).Error; err != nil {
		return InstanceResponse{}, err
	}
	response := s.newInstanceResponse(instance, product, group, rewards, true)
	response.UpstreamError = syncError
	if syncResult != nil {
		response.Upstream = syncResult.Payload
		response.UpstreamPower = syncResult.Power
		response.SecurityGroupID = syncResult.SecurityGroupID
		response.SecurityGroupName = syncResult.SecurityGroupName
		if syncResult.RootPassword != nil {
			response.RootPassword = syncResult.RootPassword
		}
	}
	return response, nil
}

func (s *Service) UpdateAutoRenew(ctx context.Context, userID int64, instanceID int64, enabled bool) (UpdateAutoRenewResponse, error) {
	if err := s.requireDB(); err != nil {
		return UpdateAutoRenewResponse{}, err
	}
	value := 0
	if enabled {
		value = 1
	}
	var instance Instance
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND user_id = ?", instanceID, userID).
			Scopes(publicVisibleInstanceScope).
			First(&instance).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return NotFound("实例不存在")
			}
			return err
		}
		if instance.Status == "terminated" {
			return BadRequest("已销毁实例不能修改自动续费")
		}
		instance.AutoRenew = value
		return tx.Save(&instance).Error
	})
	if err != nil {
		return UpdateAutoRenewResponse{}, err
	}
	return UpdateAutoRenewResponse{ID: instance.ID, AutoRenew: instance.AutoRenew}, nil
}

func (s *Service) PreviewRenew(ctx context.Context, userID int64, instanceID int64, cycles int) (RenewPreviewResponse, error) {
	if err := s.requireDB(); err != nil {
		return RenewPreviewResponse{}, err
	}
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return RenewPreviewResponse{}, err
	}
	user, err := s.userAccount(ctx, userID)
	if err != nil {
		return RenewPreviewResponse{}, err
	}
	return renewPreview(instance, user.Balance, cycles, time.Now().UTC())
}

func (s *Service) RenewInstance(ctx context.Context, userID int64, instanceID int64, cycles int) (RenewInstanceResponse, error) {
	return s.renewInstance(ctx, userID, instanceID, cycles, "manual", nil)
}

func (s *Service) renewInstance(ctx context.Context, userID int64, instanceID int64, cycles int, source string, expectedExpiresAt *time.Time) (RenewInstanceResponse, error) {
	if err := s.requireDB(); err != nil {
		return RenewInstanceResponse{}, err
	}
	if source == "" {
		source = "manual"
	}
	var out RenewInstanceResponse
	var renewedInstance Instance
	var renewUpstreamPayload map[string]any
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := &Service{db: tx, redis: s.redis, logger: s.logger, plugins: s.plugins, identity: s.identity}
		var instance Instance
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND user_id = ?", instanceID, userID).
			Scopes(publicVisibleInstanceScope).
			First(&instance).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return NotFound("实例不存在")
			}
			return err
		}
		var user UserAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", userID).First(&user).Error; err != nil {
			return err
		}
		preview, err := renewPreview(instance, user.Balance, cycles, time.Now().UTC())
		if err != nil {
			return err
		}
		if !preview.EnoughBalance {
			return HTTPError{Status: 402, Detail: fmt.Sprintf("余额不足，需 %.2f 元，当前 %.2f 元", float64(preview.TotalAmount)/100, float64(user.Balance)/100)}
		}

		var autoEvent *AutoRenewalEvent
		if source == "auto_renew" {
			if expectedExpiresAt == nil {
				return BadRequest("自动续费缺少原到期时间")
			}
			eventKey := autoRenewalEventKey(instance.ID, *expectedExpiresAt, preview.Cycles)
			var existing AutoRenewalEvent
			err := tx.Where("event_key = ?", eventKey).First(&existing).Error
			if err == nil {
				return ErrAutoRenewalAlreadyProcessed
			}
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
			autoEvent = &AutoRenewalEvent{
				EventKey:     eventKey,
				UserID:       userID,
				InstanceID:   instance.ID,
				Status:       "processing",
				Cycles:       preview.Cycles,
				AmountCents:  preview.TotalAmount,
				OldExpiresAt: expectedExpiresAt.UTC(),
				NewExpiresAt: preview.NewExpiresAt,
			}
			if err := tx.Create(autoEvent).Error; err != nil {
				if isDuplicateKeyError(err) {
					return ErrAutoRenewalAlreadyProcessed
				}
				return err
			}
		}

		balanceAfter := user.Balance - preview.TotalAmount
		if err := tx.Model(&UserAccount{}).Where("id = ?", userID).Update("balance", balanceAfter).Error; err != nil {
			return err
		}
		orderNo := generateRenewOrderNo()
		if source == "auto_renew" {
			orderNo = generateAutoRenewOrderNo()
		}
		snapshot := []map[string]any{{
			"type":                  "renewal",
			"source":                source,
			"instance_id":           instance.ID,
			"product_id":            instance.ProductID,
			"cycle":                 instance.Cycle,
			"cycles":                preview.Cycles,
			"unit_price_cents":      preview.UnitAmount,
			"item_total_cents":      preview.TotalAmount,
			"old_expires_at":        timeOrNilString(instance.ExpiresAt),
			"new_expires_at":        timeOrNilString(preview.NewExpiresAt),
			"old_next_renew_amount": instance.NextRenewAmount,
			"old_status":            instance.Status,
			"old_suspend_type":      instance.SuspendType,
		}}
		remark := fmt.Sprintf("实例 #%d 续费 %d 个周期", instance.ID, preview.Cycles)
		order := Order{
			UserID:        userID,
			OutOrderNo:    orderNo,
			Status:        "paid",
			TotalAmount:   preview.TotalAmount,
			ItemsSnapshot: jsonRawFromAny(snapshot),
			Remark:        &remark,
		}
		if err := tx.Create(&order).Error; err != nil {
			return err
		}
		if err := tx.Create(&BalanceLog{
			UserID:       userID,
			ChangeType:   "order_pay",
			Delta:        -preview.TotalAmount,
			BalanceAfter: balanceAfter,
			RefNo:        &orderNo,
			Remark:       &remark,
		}).Error; err != nil {
			return err
		}
		if err := txService.recordSpendEvent(ctx, userID, order.ID, orderNo, "server_renewal", preview.TotalAmount, remark, map[string]any{
			"instance_id": instance.ID,
			"cycles":      preview.Cycles,
		}); err != nil {
			return err
		}
		if err := txService.calculateOrderSettlement(ctx, &order, userID, preview.TotalAmount); err != nil {
			return err
		}
		if err := txService.freezeAgentSubsidyForOrder(ctx, order); err != nil {
			return err
		}
		if err := txService.CreateMemberCommissionSettlementForOrder(ctx, order.ID); err != nil {
			return err
		}
		if err := txService.FreezeOrderSettlements(ctx, order.ID); err != nil {
			return err
		}
		if err := txService.createSelfPurchasePointReward(ctx, userID, order.ID, orderNo, instance.ID, preview.TotalAmount, "server_renewal", "claimable"); err != nil {
			return err
		}

		instance.ExpiresAt = preview.NewExpiresAt
		instance.NextRenewAmount = preview.UnitAmount
		if instance.Status == "suspended" && instance.SuspendType != nil && *instance.SuspendType == "due" {
			instance.Status = "active"
			instance.SuspendType = nil
		}
		if err := tx.Save(&instance).Error; err != nil {
			return err
		}
		if autoEvent != nil {
			now := time.Now().UTC()
			autoEvent.OrderID = &order.ID
			autoEvent.Status = "success"
			autoEvent.ProcessedAt = &now
			autoEvent.NewExpiresAt = preview.NewExpiresAt
			message := "自动续费成功"
			autoEvent.Message = &message
			if err := tx.Save(autoEvent).Error; err != nil {
				return err
			}
		}
		renewedInstance = instance
		renewUpstreamPayload = map[string]any{
			"cycles":         preview.Cycles,
			"amount":         preview.TotalAmount,
			"old_expires_at": snapshot[0]["old_expires_at"],
			"new_expires_at": snapshot[0]["new_expires_at"],
		}
		out = RenewInstanceResponse{
			OrderID:    order.ID,
			OrderNo:    orderNo,
			Amount:     preview.TotalAmount,
			Balance:    balanceAfter,
			ExpiresAt:  *preview.NewExpiresAt,
			InstanceID: instance.ID,
		}
		return nil
	})
	if err != nil {
		return out, err
	}
	orderID := out.OrderID
	if _, upstreamErr := s.submitRenewUpstreamAction(ctx, renewedInstance, &orderID, intValueAny(renewUpstreamPayload["cycles"], 1), out.Amount, renewUpstreamPayload); upstreamErr != nil {
		if compensateErr := s.compensateAdvancedPaidOrder(ctx, orderID, upstreamErr.Error()); compensateErr != nil {
			return out, fmt.Errorf("renew upstream action failed: %w; compensation failed: %v", upstreamErr, compensateErr)
		}
		return out, upstreamErr
	}
	return out, nil
}

func autoRenewalEventKey(instanceID int64, expiresAt time.Time, cycles int) string {
	return fmt.Sprintf("auto_renew:%d:%s:%d", instanceID, expiresAt.UTC().Format("20060102150405"), cycles)
}

func (s *Service) PreviewInstanceRefund(ctx context.Context, userID int64, instanceID int64) (RefundPolicyResponse, error) {
	if err := s.requireDB(); err != nil {
		return RefundPolicyResponse{}, err
	}
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return RefundPolicyResponse{}, err
	}
	var order Order
	err = s.db.WithContext(ctx).Where("id = ? AND user_id = ?", instance.OrderID, userID).First(&order).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return RefundPolicyResponse{}, NotFound("订单不存在")
	}
	if err != nil {
		return RefundPolicyResponse{}, err
	}
	refund, err := s.latestRefund(ctx, order.ID)
	if err != nil {
		return RefundPolicyResponse{}, err
	}
	return s.refundPolicy(ctx, order, []Instance{instance}, refund, time.Now().UTC()), nil
}

func (s *Service) RefundInstance(ctx context.Context, userID int64, instanceID int64, req RefundInstanceRequest) (RefundInstanceResponse, error) {
	if err := s.requireDB(); err != nil {
		return RefundInstanceResponse{}, err
	}
	var out RefundInstanceResponse
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := s.withDB(tx)
		var user UserAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", userID).First(&user).Error; err != nil {
			return err
		}
		var instance Instance
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND user_id = ?", instanceID, userID).
			Scopes(publicVisibleInstanceScope).
			First(&instance).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return NotFound("实例不存在")
			}
			return err
		}
		var order Order
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND user_id = ?", instance.OrderID, userID).
			First(&order).Error; err != nil {
			return err
		}
		refund, policy, err := txService.refundServiceOrderTx(ctx, &user, &order, []Instance{instance}, req.Reason, true, false)
		if err != nil {
			return err
		}
		out = RefundInstanceResponse{
			Refund:         newRefundResponse(refund),
			RefundPolicy:   policy,
			Balance:        user.Balance,
			InstanceID:     instance.ID,
			InstanceStatus: "terminated",
		}
		return nil
	})
	return out, err
}

func (s *Service) ListServiceOrders(ctx context.Context, userID int64) ([]map[string]any, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var orders []Order
	if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Order("id DESC").Find(&orders).Error; err != nil {
		return nil, err
	}
	out := make([]map[string]any, 0, len(orders))
	for _, order := range orders {
		instances, err := s.orderInstances(ctx, userID, order.ID)
		if err != nil {
			return nil, err
		}
		refund, err := s.latestRefund(ctx, order.ID)
		if err != nil {
			return nil, err
		}
		out = append(out, s.newServiceOrderMap(ctx, order, instances, refund))
	}
	return out, nil
}

func (s *Service) UnsubscribeOrder(ctx context.Context, userID int64, orderID int64) (map[string]any, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var out map[string]any
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := s.withDB(tx)
		var user UserAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", userID).First(&user).Error; err != nil {
			return err
		}
		var order Order
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return NotFound("订单不存在")
			}
			return err
		}
		instances, err := txService.orderInstancesForOrder(ctx, order, true)
		if err != nil {
			return err
		}
		if len(instances) == 0 {
			return BadRequest("订单没有可退订实例")
		}

		orders := []Order{}
		if isServerPurchaseOrder(order) {
			related, err := txService.relatedPaidOrdersForInstances(ctx, order, instances)
			if err != nil {
				return err
			}
			orders = append(orders, sortOrdersByDependency(related)...)
		}
		orders = append(orders, order)

		refunds := make([]RefundResponse, 0, len(orders))
		totalRefund := 0
		var primaryRefund OrderRefund
		for index := range orders {
			current := &orders[index]
			currentInstances, err := txService.orderInstancesForOrder(ctx, *current, true)
			if err != nil {
				return err
			}
			terminate := current.ID == order.ID
			refund, _, err := txService.refundServiceOrderTx(ctx, &user, current, currentInstances, nil, terminate, !terminate)
			if err != nil {
				return fmt.Errorf("订单 %s 退订失败：%w", current.OutOrderNo, err)
			}
			refunds = append(refunds, newRefundResponse(refund))
			totalRefund += refund.RefundAmount
			if terminate {
				primaryRefund = refund
				order = *current
			}
		}
		updatedInstances, err := txService.orderInstancesForOrder(ctx, order, false)
		if err != nil {
			return err
		}
		primaryResponse := newRefundResponse(primaryRefund)
		out = map[string]any{
			"refund":           primaryResponse,
			"refunds":          refunds,
			"total_refund":     totalRefund,
			"instance_deleted": true,
			"order":            txService.newServiceOrderMap(ctx, order, updatedInstances, &primaryRefund),
		}
		return nil
	})
	return out, err
}

func (s *Service) refundServiceOrderTx(ctx context.Context, user *UserAccount, order *Order, instances []Instance, reasonOverride *string, terminate bool, cascade bool) (OrderRefund, RefundPolicyResponse, error) {
	latest, err := s.latestRefund(ctx, order.ID)
	if err != nil {
		return OrderRefund{}, RefundPolicyResponse{}, err
	}
	policy := s.refundPolicy(ctx, *order, instances, latest, time.Now().UTC())
	if !policy.ActionAllowed {
		return OrderRefund{}, policy, BadRequest(policy.Reason)
	}
	reason := policy.Reason
	if reasonOverride != nil && strings.TrimSpace(*reasonOverride) != "" {
		reason = strings.TrimSpace(*reasonOverride)
	}
	now := time.Now().UTC()
	refundNo := generateRefundNo()
	resourceStatus := "success"
	var resourceError *string
	if terminate {
		resourceStatus = "failed"
		message := unsupportedProviderMessage
		resourceError = &message
	}
	instanceIDs := make([]int64, 0, len(instances))
	for _, instance := range instances {
		instanceIDs = append(instanceIDs, instance.ID)
	}
	refund := OrderRefund{
		RefundNo:            refundNo,
		UserID:              user.ID,
		OrderID:             order.ID,
		RefundType:          policy.RefundType,
		RefundAmount:        policy.RefundAmount,
		Status:              "success",
		ResourceStatus:      resourceStatus,
		ResourceError:       resourceError,
		ResourceCompletedAt: &now,
		Reason:              &reason,
		PayloadSnapshot: jsonRawFromAny(map[string]any{
			"policy":       policy,
			"instance_ids": instanceIDs,
			"cascade":      cascade,
			"go_server":    true,
		}),
	}
	if err := s.db.WithContext(ctx).Create(&refund).Error; err != nil {
		return OrderRefund{}, policy, err
	}
	if policy.RefundAmount > 0 {
		user.Balance += policy.RefundAmount
		if err := s.db.WithContext(ctx).Model(&UserAccount{}).Where("id = ?", user.ID).Update("balance", user.Balance).Error; err != nil {
			return OrderRefund{}, policy, err
		}
		if err := s.db.WithContext(ctx).Create(&BalanceLog{UserID: user.ID, ChangeType: "order_refund", Delta: policy.RefundAmount, BalanceAfter: user.Balance, RefNo: &refundNo, Remark: &reason}).Error; err != nil {
			return OrderRefund{}, policy, err
		}
		ratio := 10000
		if policy.PaidAmount > 0 {
			ratio = minInt(10000, policy.RefundAmount*10000/policy.PaidAmount)
		}
		if err := s.reverseSpendEventsForOrder(ctx, user.ID, order.ID, refundNo, reason, ratio); err != nil {
			return OrderRefund{}, policy, err
		}
		if err := s.refundUsedPointsForOrder(ctx, user.ID, order.ID, order.OutOrderNo, ratio, reason); err != nil {
			return OrderRefund{}, policy, err
		}
		if err := s.reverseOrderPointRewards(ctx, order.ID, reason); err != nil {
			return OrderRefund{}, policy, err
		}
		if err := s.reverseOrderSettlements(ctx, order, reason); err != nil {
			return OrderRefund{}, policy, err
		}
		order.Status = "refunded"
	} else {
		if err := s.cancelUnclaimedOrderPointRewards(ctx, order.ID, reason); err != nil {
			return OrderRefund{}, policy, err
		}
		order.Status = "cancelled"
	}
	order.Remark = &reason
	if err := s.db.WithContext(ctx).Save(order).Error; err != nil {
		return OrderRefund{}, policy, err
	}
	if !terminate {
		if err := s.db.WithContext(ctx).Table("instance_disk_addons").Where("order_id = ? AND status IN ?", order.ID, []string{"pending", "active"}).Updates(map[string]any{"status": "refunded", "refunded_at": now, "remark": reason, "updated_at": now}).Error; err != nil {
			return OrderRefund{}, policy, err
		}
		return refund, policy, nil
	}
	for index := range instances {
		instance := &instances[index]
		instance.Status = "terminated"
		instance.TerminatedAt = &now
		if err := s.db.WithContext(ctx).Save(instance).Error; err != nil {
			return OrderRefund{}, policy, err
		}
		if _, err := s.RestockReleasedInstanceTx(ctx, instance.ID, "refund", reason, refund.RefundNo); err != nil {
			return OrderRefund{}, policy, err
		}
		orderID := order.ID
		refundID := refund.ID
		if _, err := s.submitTerminateUpstreamAction(ctx, *instance, &orderID, &refundID, map[string]any{"refund_no": refund.RefundNo, "refund_type": refund.RefundType, "refund_amount": refund.RefundAmount, "reason": reason}); err != nil && s.logger != nil {
			s.logger.Warn("refund upstream terminate action failed", "instance_id", instance.ID, "refund_id", refund.ID, "error", err)
		}
	}
	if err := s.refreshRefundResourceStatus(ctx, refund.ID); err != nil {
		return OrderRefund{}, policy, err
	}
	if err := s.db.WithContext(ctx).Where("id = ?", refund.ID).First(&refund).Error; err != nil {
		return OrderRefund{}, policy, err
	}
	return refund, policy, nil
}

func (s *Service) userInstance(ctx context.Context, userID int64, instanceID int64) (Instance, error) {
	var instance Instance
	err := s.db.WithContext(ctx).
		Where("id = ? AND user_id = ?", instanceID, userID).
		Scopes(publicVisibleInstanceScope).
		First(&instance).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return Instance{}, NotFound("实例不存在")
	}
	return instance, err
}

func (s *Service) orderInstances(ctx context.Context, userID int64, orderID int64) ([]Instance, error) {
	var order Order
	if err := s.db.WithContext(ctx).Select("id", "user_id", "items_snapshot").Where("id = ? AND user_id = ?", orderID, userID).First(&order).Error; err != nil {
		return nil, err
	}
	return s.orderInstancesForOrder(ctx, order, false)
}

func (s *Service) orderInstancesForOrder(ctx context.Context, order Order, forUpdate bool) ([]Instance, error) {
	var instances []Instance
	db := s.db.WithContext(ctx).Where("user_id = ?", order.UserID)
	instanceIDs := instanceIDsFromOrderItems(order.ItemsSnapshot)
	if len(instanceIDs) > 0 {
		db = db.Where("order_id = ? OR id IN ?", order.ID, instanceIDs)
	} else {
		db = db.Where("order_id = ?", order.ID)
	}
	if forUpdate {
		db = db.Clauses(clause.Locking{Strength: "UPDATE"})
	}
	err := db.
		Scopes(publicVisibleInstanceScope).
		Order("id ASC").
		Find(&instances).Error
	return instances, err
}

func (s *Service) relatedPaidOrdersForInstances(ctx context.Context, order Order, instances []Instance) ([]Order, error) {
	instanceIDs := map[int64]struct{}{}
	for _, instance := range instances {
		instanceIDs[instance.ID] = struct{}{}
	}
	var candidates []Order
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id = ? AND id <> ? AND status = ?", order.UserID, order.ID, "paid").Order("id ASC").Find(&candidates).Error; err != nil {
		return nil, err
	}
	related := make([]Order, 0)
	for _, candidate := range candidates {
		for _, instanceID := range instanceIDsFromOrderItems(candidate.ItemsSnapshot) {
			if _, ok := instanceIDs[instanceID]; ok {
				related = append(related, candidate)
				break
			}
		}
	}
	return related, nil
}

func sortOrdersByDependency(orders []Order) []Order {
	out := append([]Order(nil), orders...)
	sort.SliceStable(out, func(i, j int) bool {
		left, right := orderDependencyRank(out[i]), orderDependencyRank(out[j])
		if left == right {
			return out[i].ID < out[j].ID
		}
		return left < right
	})
	return out
}

func orderDependencyRank(order Order) int {
	best := 5
	for _, item := range jsonList(order.ItemsSnapshot) {
		row, ok := item.(map[string]any)
		if !ok {
			continue
		}
		itemType := strings.TrimSpace(firstString(row["type"], "server_purchase"))
		if rank := orderTypeRank(itemType); rank < best {
			best = rank
		}
	}
	return best
}

func orderTypeRank(itemType string) int {
	switch itemType {
	case "package_upgrade":
		return 0
	case "disk_resize":
		return 1
	case "disk_addon":
		return 2
	case "server_purchase":
		return 3
	default:
		return 4
	}
}

func isServerPurchaseOrder(order Order) bool {
	for _, item := range jsonList(order.ItemsSnapshot) {
		row, ok := item.(map[string]any)
		if ok && strings.TrimSpace(firstString(row["type"], "server_purchase")) == "server_purchase" {
			return true
		}
	}
	return false
}

func (s *Service) userAccount(ctx context.Context, userID int64) (UserAccount, error) {
	var user UserAccount
	err := s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return UserAccount{}, Unauthorized("Could not validate credentials")
	}
	return user, err
}

func (s *Service) productAndGroup(ctx context.Context, productID int64) (*Product, *ProductGroup, error) {
	var product Product
	err := s.db.WithContext(ctx).Where("id = ?", productID).First(&product).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil, nil
	}
	if err != nil {
		return nil, nil, err
	}
	var group ProductGroup
	err = s.db.WithContext(ctx).Where("id = ?", product.GroupID).First(&group).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return &product, nil, nil
	}
	if err != nil {
		return nil, nil, err
	}
	return &product, &group, nil
}

func (s *Service) newInstanceResponse(instance Instance, product *Product, group *ProductGroup, rewards []PointReward, detail bool) InstanceResponse {
	productName := fmt.Sprintf("产品#%d", instance.ProductID)
	apiType := ""
	provisionConfig := []any{}
	var productGroupName *string
	var provisionPlugin map[string]any
	pluginManageActions := []any{}
	if product != nil {
		productName = product.Name
		apiType = product.APIType
		provisionConfig = jsonList(product.ProvisionConfig)
		provisionPlugin = s.publicPluginForProduct(*product)
		pluginManageActions = userVisiblePluginManageActions(provisionPlugin)
	}
	if group != nil {
		productGroupName = &group.Name
	}
	createdAt := instance.CreatedAt
	renewal, _ := renewPreview(instance, 0, 1, time.Now().UTC())
	out := InstanceResponse{
		ID:               instance.ID,
		Status:           instance.Status,
		Hostname:         instance.Hostname,
		MainIP:           instance.MainIP,
		SSHPort:          instance.SSHPort,
		OSImage:          instance.OSImage,
		OSName:           osLabel(instance.OSImage),
		ExpiresAt:        instance.ExpiresAt,
		CreatedAt:        &createdAt,
		ProductName:      productName,
		ProductGroupName: productGroupName,
		CPU: firstInt(instance.CPU, func() *int {
			if product != nil {
				return product.VCPU
			}
			return nil
		}()),
		Memory: firstInt(instance.Memory, func() *int {
			if product != nil {
				return parseMemoryMB(product.Memory)
			}
			return nil
		}()),
		UpstreamID:          instance.UpstreamID,
		Cycle:               instance.Cycle,
		UnitPrice:           instance.UnitPrice,
		NextRenewAmount:     effectiveRenewAmount(instance),
		AutoRenew:           instance.AutoRenew,
		SystemDiskSize:      instance.SystemDiskSize,
		DataDiskSize:        instance.DataDiskSize,
		InBW:                instance.InBW,
		OutBW:               instance.OutBW,
		Internet:            instance.Internet,
		NatACLLimit:         instance.NatACLLimit,
		IsLocked:            instance.IsLocked,
		SuspendType:         instance.SuspendType,
		APIType:             apiType,
		ProvisionConfig:     provisionConfig,
		ProvisionPlugin:     provisionPlugin,
		PluginManageActions: pluginManageActions,
		PluginLinks:         pluginLinks(instance.UpstreamPayload),
		UpstreamPayload:     jsonAnyValue(instance.UpstreamPayload),
		ProvisionLog:        instance.ProvisionLog,
		Renewal:             renewal,
	}
	if detail && instance.RootPassword != nil && strings.TrimSpace(*instance.RootPassword) != "" {
		if password, err := decryptStoredSecret(*instance.RootPassword); err == nil && password != "" {
			out.RootPassword = &password
		}
	}
	if group != nil {
		out.ProductGroupArea = group.Area
		out.ProductGroupNode = group.Node
		out.ProductGroupNetworkType = group.NetworkType
	}
	for _, reward := range rewards {
		out.PointRewards = append(out.PointRewards, newPointRewardResponse(reward))
	}
	if !detail {
		out.UpstreamPayload = nil
		out.ProvisionLog = nil
	}
	return out
}

func renewPreview(instance Instance, balance int, cycles int, now time.Time) (RenewPreviewResponse, error) {
	if instance.Status != "active" && instance.Status != "suspended" {
		return RenewPreviewResponse{}, BadRequest("当前实例状态不支持续费")
	}
	if cycles <= 0 {
		cycles = 1
	}
	if cycles > 36 {
		return RenewPreviewResponse{}, BadRequest("续费周期数不合法")
	}
	unit := effectiveRenewAmount(instance)
	if unit <= 0 {
		return RenewPreviewResponse{}, BadRequest("续费金额异常，请联系管理员")
	}
	base := now
	if instance.ExpiresAt != nil && instance.ExpiresAt.After(now) {
		base = *instance.ExpiresAt
	}
	next := base.AddDate(0, 0, cycleDaysLocal(instance.Cycle)*cycles)
	total := unit * cycles
	return RenewPreviewResponse{
		InstanceID:       instance.ID,
		Cycles:           cycles,
		UnitAmount:       unit,
		TotalAmount:      total,
		Balance:          balance,
		EnoughBalance:    balance >= total,
		CurrentExpiresAt: instance.ExpiresAt,
		NewExpiresAt:     &next,
	}, nil
}

func effectiveRenewAmount(instance Instance) int {
	if instance.NextRenewAmount > 0 {
		return instance.NextRenewAmount
	}
	return instance.UnitPrice
}

func cycleDaysLocal(cycle string) int {
	switch strings.ToLower(strings.TrimSpace(cycle)) {
	case "hourly", "daily":
		return 1
	case "weekly":
		return 7
	case "quarterly":
		return 90
	case "semi_annually", "semiannually":
		return 180
	case "annually", "yearly":
		return 365
	case "biennially":
		return 730
	case "triennially":
		return 1095
	case "one_time":
		return 3650
	default:
		return 30
	}
}

func (s *Service) latestRefund(ctx context.Context, orderID int64) (*OrderRefund, error) {
	var refund OrderRefund
	err := s.db.WithContext(ctx).Where("order_id = ?", orderID).Order("id DESC").First(&refund).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &refund, err
}

func (s *Service) refundPolicy(ctx context.Context, order Order, instances []Instance, latest *OrderRefund, now time.Time) RefundPolicyResponse {
	fullRefundDays := s.settlementFreezeDays(ctx)
	paid := maxInt(0, order.TotalAmount-order.DiscountAmount)
	latestResponse := (*RefundResponse)(nil)
	if latest != nil {
		value := newRefundResponse(*latest)
		latestResponse = &value
	}
	policy := RefundPolicyResponse{
		ActionAllowed:          false,
		RefundType:             "unavailable",
		RefundAmount:           0,
		PaidAmount:             paid,
		Reason:                 "当前订单不可退订",
		FullRefundDays:         fullRefundDays,
		WithinFullRefundWindow: now.Sub(order.CreatedAt) <= time.Duration(fullRefundDays)*24*time.Hour,
		LatestRefund:           latestResponse,
	}
	if latest != nil {
		policy.Reason = "订单已退订"
		return policy
	}
	if order.Status != "paid" {
		policy.Reason = "订单状态不支持退订"
		return policy
	}
	if len(instances) == 0 {
		policy.Reason = "订单没有可退订实例"
		return policy
	}
	if policy.WithinFullRefundWindow {
		policy.ActionAllowed = true
		policy.RefundType = "full"
		policy.RefundAmount = paid
		policy.Reason = "全额退款窗口内，可全额退订"
		return policy
	}
	prorated := proratedRefund(order, instances, now)
	if prorated > 0 {
		policy.ActionAllowed = true
		policy.RefundType = "prorated"
		policy.RefundAmount = prorated
		policy.Reason = "按剩余服务时间折算退款"
		return policy
	}
	policy.ActionAllowed = true
	policy.RefundType = "no_refund"
	policy.Reason = "服务已无可退金额，将仅取消服务"
	return policy
}

func proratedRefund(order Order, instances []Instance, now time.Time) int {
	paid := maxInt(0, order.TotalAmount-order.DiscountAmount)
	if paid <= 0 {
		return 0
	}
	total := 0
	for _, instance := range instances {
		if instance.ExpiresAt == nil || !instance.ExpiresAt.After(now) {
			continue
		}
		cycleSeconds := cycleDaysLocal(instance.Cycle) * 24 * 3600
		if cycleSeconds <= 0 {
			continue
		}
		remaining := int(instance.ExpiresAt.Sub(now).Seconds())
		total += instance.UnitPrice * remaining / cycleSeconds
	}
	return minInt(paid, maxInt(0, total))
}

func (s *Service) recordSpendEvent(ctx context.Context, userID int64, orderID int64, orderNo string, eventType string, amount int, remark string, payload map[string]any) error {
	if amount == 0 {
		return nil
	}
	if err := s.ensureSpendEventTypeConfig(ctx, eventType); err != nil {
		return err
	}
	eventKey := fmt.Sprintf("%s:%s", orderNo, eventType)
	var existing UserSpendEvent
	err := s.db.WithContext(ctx).Where("event_key = ?", eventKey).First(&existing).Error
	if err == nil {
		return nil
	}
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}
	event := UserSpendEvent{
		UserID:          userID,
		EventType:       eventType,
		EventKey:        eventKey,
		AmountCents:     amount,
		OrderID:         &orderID,
		RefNo:           &orderNo,
		Remark:          &remark,
		PayloadSnapshot: jsonRawFromAny(payload),
	}
	if err := s.db.WithContext(ctx).Create(&event).Error; err != nil {
		return err
	}
	return s.recalculateMembershipSpent(ctx, userID)
}

func (s *Service) reverseSpendEventsForOrder(ctx context.Context, userID int64, orderID int64, refundNo string, reason string, ratioBps int) error {
	ratioBps = maxInt(0, minInt(10000, ratioBps))
	if ratioBps <= 0 {
		return nil
	}
	var events []UserSpendEvent
	if err := s.db.WithContext(ctx).Where("user_id = ? AND order_id = ? AND amount_cents > 0", userID, orderID).Find(&events).Error; err != nil {
		return err
	}
	for _, event := range events {
		amount := event.AmountCents * ratioBps / 10000
		if amount <= 0 {
			continue
		}
		key := fmt.Sprintf("%s:refund:%s", event.EventKey, refundNo)
		var existing UserSpendEvent
		err := s.db.WithContext(ctx).Where("event_key = ?", key).First(&existing).Error
		if err == nil {
			continue
		}
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		remark := fmt.Sprintf("订单退款反冲有效消费：%s", reason)
		reverse := UserSpendEvent{
			UserID:          userID,
			EventType:       event.EventType,
			EventKey:        key,
			AmountCents:     -amount,
			OrderID:         &orderID,
			RefNo:           &refundNo,
			Remark:          &remark,
			PayloadSnapshot: jsonRawFromAny(map[string]any{"refund_no": refundNo, "source_event_id": event.ID}),
		}
		if err := s.db.WithContext(ctx).Create(&reverse).Error; err != nil {
			return err
		}
	}
	return s.recalculateMembershipSpent(ctx, userID)
}

func (s *Service) refundUsedPointsForOrder(ctx context.Context, userID int64, orderID int64, orderNo string, ratioBps int, reason string) error {
	ratioBps = maxInt(0, minInt(10000, ratioBps))
	if ratioBps <= 0 {
		return nil
	}
	var existing int64
	if err := s.db.WithContext(ctx).Model(&PointLog{}).Where("user_id = ? AND order_id = ? AND change_type = ?", userID, orderID, "points_refund").Count(&existing).Error; err != nil {
		return err
	}
	if existing > 0 {
		return nil
	}
	var logs []PointLog
	if err := s.db.WithContext(ctx).Where("user_id = ? AND order_id = ? AND change_type = ?", userID, orderID, "points_use").Find(&logs).Error; err != nil {
		return err
	}
	used := 0
	for _, log := range logs {
		if log.Delta < 0 {
			used += -log.Delta
		}
	}
	points := used * ratioBps / 10000
	if points <= 0 {
		return nil
	}
	_, err := s.addPoints(ctx, userID, points, "points_refund", orderID, orderNo+":points_refund", fmt.Sprintf("订单退订返还积分抵扣：%s", reason))
	return err
}

func (s *Service) cancelUnclaimedOrderPointRewards(ctx context.Context, orderID int64, reason string) error {
	now := time.Now().UTC()
	return s.db.WithContext(ctx).Model(&PointReward{}).
		Where("order_id = ? AND status IN ?", orderID, []string{"pending", "claimable"}).
		Updates(map[string]any{"status": "cancelled", "cancelled_at": now, "remark": reason}).Error
}

func (s *Service) reverseOrderPointRewards(ctx context.Context, orderID int64, reason string) error {
	var rewards []PointReward
	if err := s.db.WithContext(ctx).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("order_id = ? AND status IN ?", orderID, []string{"pending", "claimable", "claimed"}).
		Find(&rewards).Error; err != nil {
		return err
	}
	now := time.Now().UTC()
	for _, reward := range rewards {
		if reward.Status == "claimed" {
			log, err := s.addPoints(ctx, reward.UserID, -reward.Points, "order_refund", reward.OrderID, valueOrDefault(reward.RefNo, fmt.Sprint(reward.OrderID))+":reward_reverse", fmt.Sprintf("自购返积分扣回：%s", reason))
			if err != nil {
				return err
			}
			reward.Status = "reversed"
			reward.ReversePointLogID = &log.ID
			reward.ReversedAt = &now
		} else {
			reward.Status = "cancelled"
			reward.CancelledAt = &now
		}
		reward.Remark = &reason
		if err := s.db.WithContext(ctx).Save(&reward).Error; err != nil {
			return err
		}
	}
	return nil
}

func (s *Service) reverseOrderSettlements(ctx context.Context, order *Order, reason string) error {
	var settlements []AgentSettlement
	if err := s.db.WithContext(ctx).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("order_id = ? AND status IN ?", order.ID, []string{"pending", "frozen", "settled"}).
		Find(&settlements).Error; err != nil {
		return err
	}
	now := time.Now().UTC()
	for _, settlement := range settlements {
		if settlement.SettlementType == "agent_subsidy" {
			if err := s.reverseAgentSubsidy(ctx, order, settlement); err != nil {
				return err
			}
		} else if settlement.SettlementType == "agent_profit" && settlement.Status == "settled" && settlement.DeltaAmount > 0 {
			if err := s.reverseAgentProfit(ctx, order, settlement); err != nil {
				return err
			}
		}
		if settlement.PointLogID != nil && settlement.PointsAmount > 0 {
			_, err := s.addPoints(ctx, settlement.UserID, -settlement.PointsAmount, "order_refund", order.ID, fmt.Sprintf("%s:agent_reverse:%d", order.OutOrderNo, settlement.ID), fmt.Sprintf("代理/推广收益退订扣回：%s", reason))
			if err != nil {
				return err
			}
		}
		settlement.Status = "reversed"
		settlement.ReversedAt = &now
		settlement.Remark = &reason
		if err := s.db.WithContext(ctx).Save(&settlement).Error; err != nil {
			return err
		}
	}
	if len(settlements) > 0 {
		status := "reversed"
		order.SettlementStatus = &status
	}
	return nil
}

func (s *Service) reverseAgentSubsidy(ctx context.Context, order *Order, settlement AgentSettlement) error {
	amount := absInt(settlement.DeltaAmount)
	var agent UserAgent
	err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id = ?", settlement.UserID).First(&agent).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	balanceAfter := agent.AgentBalance
	frozenAfter := agent.AgentFrozenBalance
	changeType := "subsidy_refund"
	if settlement.Status == "pending" || settlement.Status == "frozen" {
		frozenAfter = maxInt(0, agent.AgentFrozenBalance-amount)
		balanceAfter = agent.AgentBalance + amount
		changeType = "subsidy_release"
	} else {
		balanceAfter = agent.AgentBalance + amount
	}
	if err := s.db.WithContext(ctx).Model(&UserAgent{}).Where("id = ?", agent.ID).Updates(map[string]any{
		"agent_balance":        balanceAfter,
		"agent_frozen_balance": frozenAfter,
	}).Error; err != nil {
		return err
	}
	remark := fmt.Sprintf("订单 %s 退订返还/释放代理补贴 %d 分", order.OutOrderNo, amount)
	return s.db.WithContext(ctx).Create(&AgentBalanceLog{
		UserID:             settlement.UserID,
		ChangeType:         changeType,
		Delta:              amount,
		BalanceAfter:       balanceAfter,
		FrozenBalanceAfter: frozenAfter,
		OrderID:            &order.ID,
		RefNo:              &order.OutOrderNo,
		Remark:             &remark,
	}).Error
}

func (s *Service) reverseAgentProfit(ctx context.Context, order *Order, settlement AgentSettlement) error {
	amount := settlement.DeltaAmount
	var agent UserAgent
	err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id = ?", settlement.UserID).First(&agent).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	balanceAfter := agent.AgentBalance - amount
	if err := s.db.WithContext(ctx).Model(&UserAgent{}).Where("id = ?", agent.ID).Update("agent_balance", balanceAfter).Error; err != nil {
		return err
	}
	remark := fmt.Sprintf("订单 %s 退订扣回代理收益 %d 分", order.OutOrderNo, amount)
	return s.db.WithContext(ctx).Create(&AgentBalanceLog{
		UserID:             settlement.UserID,
		ChangeType:         "profit_reverse",
		Delta:              -amount,
		BalanceAfter:       balanceAfter,
		FrozenBalanceAfter: agent.AgentFrozenBalance,
		OrderID:            &order.ID,
		RefNo:              &order.OutOrderNo,
		Remark:             &remark,
	}).Error
}

func newPointRewardResponse(reward PointReward) PointRewardResponse {
	return PointRewardResponse{
		ID:          reward.ID,
		OrderID:     reward.OrderID,
		InstanceID:  reward.InstanceID,
		SourceType:  reward.SourceType,
		Status:      reward.Status,
		StatusLabel: pointRewardStatusLabel(reward.Status),
		AmountCents: reward.AmountCents,
		RewardRate:  reward.RewardRate,
		Points:      reward.Points,
		RefNo:       reward.RefNo,
		Remark:      reward.Remark,
		CreatedAt:   reward.CreatedAt,
		ClaimedAt:   reward.ClaimedAt,
	}
}

func newRefundResponse(refund OrderRefund) RefundResponse {
	return RefundResponse{
		ID:                  refund.ID,
		RefundNo:            refund.RefundNo,
		RefundType:          refund.RefundType,
		RefundAmount:        refund.RefundAmount,
		Status:              refund.Status,
		ResourceStatus:      refund.ResourceStatus,
		ResourceError:       refund.ResourceError,
		ResourceCompletedAt: refund.ResourceCompletedAt,
		Reason:              refund.Reason,
		CreatedAt:           refund.CreatedAt,
	}
}

func (s *Service) newServiceOrderMap(ctx context.Context, order Order, instances []Instance, refund *OrderRefund) map[string]any {
	policy := s.refundPolicy(ctx, order, instances, refund, time.Now().UTC())
	instanceItems := make([]map[string]any, 0, len(instances))
	content := []string{}
	for _, instance := range instances {
		instanceItems = append(instanceItems, map[string]any{
			"id":               instance.ID,
			"status":           instance.Status,
			"main_ip":          instance.MainIP,
			"hostname":         instance.Hostname,
			"expires_at":       instance.ExpiresAt,
			"alias":            instance.Alias,
			"cycle":            instance.Cycle,
			"unit_price":       instance.UnitPrice,
			"created_at":       instance.CreatedAt,
			"terminated_at":    instance.TerminatedAt,
			"cpu":              instance.CPU,
			"memory":           instance.Memory,
			"system_disk_size": instance.SystemDiskSize,
			"data_disk_size":   instance.DataDiskSize,
			"in_bw":            instance.InBW,
			"out_bw":           instance.OutBW,
			"ip_num":           instance.IPNum,
			"os_image":         instance.OSImage,
		})
		name := fmt.Sprintf("实例 #%d", instance.ID)
		if instance.Hostname != nil && *instance.Hostname != "" {
			name = *instance.Hostname
		}
		content = append(content, name)
	}
	var refundResponse *RefundResponse
	if refund != nil {
		value := newRefundResponse(*refund)
		refundResponse = &value
	}
	pointRewards := s.serviceOrderPointRewards(ctx, order.ID)
	upstreamActions := s.serviceOrderUpstreamActions(ctx, order.ID, instances, refund)
	timeline := s.serviceOrderTimeline(order, instances, refund, upstreamActions, pointRewards)
	return map[string]any{
		"id":               order.ID,
		"out_order_no":     order.OutOrderNo,
		"status":           order.Status,
		"total_amount":     order.TotalAmount,
		"discount_amount":  order.DiscountAmount,
		"paid_amount":      maxInt(0, order.TotalAmount-order.DiscountAmount),
		"created_at":       order.CreatedAt,
		"remark":           order.Remark,
		"items_snapshot":   jsonList(order.ItemsSnapshot),
		"order_type":       serviceOrderType(order),
		"dependency_rank":  orderDependencyRank(order),
		"instance_ids":     instanceIDsFromOrderItems(order.ItemsSnapshot),
		"content":          content,
		"instances":        instanceItems,
		"point_rewards":    pointRewards,
		"upstream_actions": upstreamActions,
		"timeline":         timeline,
		"points_claimed":   order.PointsClaimed,
		"refund_policy": map[string]any{
			"action_allowed":            policy.ActionAllowed,
			"refund_type":               policy.RefundType,
			"refund_amount":             policy.RefundAmount,
			"paid_amount":               policy.PaidAmount,
			"reason":                    policy.Reason,
			"points_claimed":            order.PointsClaimed,
			"full_refund_used":          0,
			"full_refund_limit":         1,
			"full_refund_days":          policy.FullRefundDays,
			"within_full_refund_window": policy.WithinFullRefundWindow,
			"latest_refund":             policy.LatestRefund,
		},
		"refund": refundResponse,
	}
}

func serviceOrderType(order Order) string {
	bestType := "server_purchase"
	bestRank := 5
	for _, item := range jsonList(order.ItemsSnapshot) {
		row, ok := item.(map[string]any)
		if !ok {
			continue
		}
		itemType := strings.TrimSpace(firstString(row["type"], "server_purchase"))
		if rank := orderTypeRank(itemType); rank < bestRank {
			bestType = itemType
			bestRank = rank
		}
	}
	return bestType
}

func (s *Service) serviceOrderPointRewards(ctx context.Context, orderID int64) []map[string]any {
	var rewards []PointReward
	if err := s.db.WithContext(ctx).Where("order_id = ?", orderID).Order("id ASC").Find(&rewards).Error; err != nil {
		return []map[string]any{}
	}
	out := make([]map[string]any, 0, len(rewards))
	for _, reward := range rewards {
		item := newPointRewardResponse(reward)
		out = append(out, map[string]any{
			"id":           item.ID,
			"order_id":     item.OrderID,
			"instance_id":  item.InstanceID,
			"source_type":  item.SourceType,
			"status":       item.Status,
			"status_label": item.StatusLabel,
			"amount_cents": item.AmountCents,
			"reward_rate":  item.RewardRate,
			"points":       item.Points,
			"ref_no":       item.RefNo,
			"remark":       item.Remark,
			"created_at":   item.CreatedAt,
			"claimed_at":   item.ClaimedAt,
		})
	}
	return out
}

func (s *Service) serviceOrderUpstreamActions(ctx context.Context, orderID int64, instances []Instance, refund *OrderRefund) []map[string]any {
	query := s.db.WithContext(ctx).Where("order_id = ?", orderID)
	instanceIDs := make([]int64, 0, len(instances))
	for _, instance := range instances {
		instanceIDs = append(instanceIDs, instance.ID)
	}
	if len(instanceIDs) > 0 {
		query = query.Or("instance_id IN ?", instanceIDs)
	}
	if refund != nil {
		query = query.Or("order_refund_id = ?", refund.ID)
	}
	var logs []UpstreamActionLog
	if err := query.Order("id DESC").Find(&logs).Error; err != nil {
		return []map[string]any{}
	}
	out := make([]map[string]any, 0, len(logs))
	for _, log := range logs {
		item := newUpstreamActionLogResponse(log)
		out = append(out, map[string]any{
			"id":                item.ID,
			"user_id":           item.UserID,
			"order_id":          item.OrderID,
			"order_refund_id":   item.OrderRefundID,
			"instance_id":       item.InstanceID,
			"cloud_server_id":   item.CloudServerID,
			"upstream_id":       item.UpstreamID,
			"action_type":       item.ActionType,
			"resource_type":     item.ResourceType,
			"resource_id":       item.ResourceID,
			"source":            item.Source,
			"status":            item.Status,
			"upstream_task_id":  item.UpstreamTaskID,
			"request_method":    item.RequestMethod,
			"request_path":      item.RequestPath,
			"request_payload":   item.RequestPayload,
			"response_payload":  item.ResponsePayload,
			"task_payload":      item.TaskPayload,
			"error_message":     item.ErrorMessage,
			"remark":            item.Remark,
			"attempt_count":     item.AttemptCount,
			"last_checked_at":   item.LastCheckedAt,
			"completed_at":      item.CompletedAt,
			"created_at":        item.CreatedAt,
			"updated_at":        item.UpdatedAt,
			"status_label":      upstreamActionStatusLabel(item.Status),
			"action_type_label": upstreamActionTypeLabel(item.ActionType),
		})
	}
	return out
}

func (s *Service) serviceOrderTimeline(order Order, instances []Instance, refund *OrderRefund, actions []map[string]any, rewards []map[string]any) []map[string]any {
	items := []map[string]any{
		{
			"key":         fmt.Sprintf("order:%d:created", order.ID),
			"type":        "order_created",
			"kind":        "order",
			"title":       "订单已创建",
			"message":     fmt.Sprintf("订单 %s 已创建", order.OutOrderNo),
			"description": fmt.Sprintf("订单 %s 已创建", order.OutOrderNo),
			"status":      order.Status,
			"created_at":  order.CreatedAt,
			"at":          order.CreatedAt,
			"ref_id":      order.ID,
		},
	}
	for _, instance := range instances {
		title := "实例已创建"
		if instance.Status == "active" {
			title = "实例已开通"
		} else if instance.Status == "provision_failed" {
			title = "实例开通失败"
		} else if instance.Status == "terminated" {
			title = "实例已释放"
		}
		items = append(items, map[string]any{
			"key":         fmt.Sprintf("instance:%d:%d", instance.ID, instance.UpdatedAt.UnixNano()),
			"type":        "instance",
			"kind":        "instance",
			"title":       title,
			"message":     fmt.Sprintf("实例 #%d 当前状态：%s", instance.ID, instance.Status),
			"description": fmt.Sprintf("实例 #%d 当前状态：%s", instance.ID, instance.Status),
			"status":      instance.Status,
			"instance_id": instance.ID,
			"created_at":  instance.UpdatedAt,
			"at":          instance.UpdatedAt,
			"ref_id":      instance.ID,
		})
	}
	for _, action := range actions {
		description := firstString(action["remark"], action["error_message"], upstreamActionStatusLabel(fmt.Sprint(action["status"])))
		items = append(items, map[string]any{
			"key":           fmt.Sprintf("upstream_action:%v", action["id"]),
			"type":          "upstream_action",
			"kind":          "resource",
			"title":         upstreamActionTypeLabel(fmt.Sprint(action["action_type"])),
			"message":       description,
			"description":   description,
			"status":        action["status"],
			"action_id":     action["id"],
			"action_type":   action["action_type"],
			"instance_id":   action["instance_id"],
			"refund_id":     action["order_refund_id"],
			"created_at":    action["created_at"],
			"completed_at":  action["completed_at"],
			"at":            firstNonNil(action["completed_at"], action["created_at"]),
			"ref_id":        action["id"],
			"status_label":  action["status_label"],
			"request_path":  action["request_path"],
			"error_message": action["error_message"],
		})
	}
	for _, reward := range rewards {
		description := fmt.Sprintf("奖励积分：%v", reward["points"])
		items = append(items, map[string]any{
			"key":         fmt.Sprintf("point_reward:%v", reward["id"]),
			"type":        "point_reward",
			"kind":        "points",
			"title":       "积分奖励",
			"message":     description,
			"description": description,
			"status":      reward["status"],
			"instance_id": reward["instance_id"],
			"reward_id":   reward["id"],
			"created_at":  reward["created_at"],
			"at":          reward["created_at"],
			"ref_id":      reward["id"],
		})
	}
	if refund != nil {
		description := fmt.Sprintf("退款金额：￥%.2f", float64(refund.RefundAmount)/100)
		items = append(items, map[string]any{
			"key":         fmt.Sprintf("refund:%d", refund.ID),
			"type":        "refund",
			"kind":        "refund",
			"title":       "退订记录",
			"message":     description,
			"description": description,
			"status":      refund.Status,
			"refund_id":   refund.ID,
			"created_at":  refund.CreatedAt,
			"at":          refund.CreatedAt,
			"ref_id":      refund.ID,
		})
	}
	sort.SliceStable(items, func(i, j int) bool {
		return timelineTime(items[i]).After(timelineTime(items[j]))
	})
	return items
}

func timelineTime(item map[string]any) time.Time {
	for _, key := range []string{"created_at", "completed_at"} {
		switch value := item[key].(type) {
		case time.Time:
			return value
		case *time.Time:
			if value != nil {
				return *value
			}
		}
	}
	return time.Time{}
}

func upstreamActionStatusLabel(status string) string {
	switch status {
	case "submitted":
		return "已提交"
	case "running":
		return "执行中"
	case "success":
		return "成功"
	case "failed":
		return "失败"
	case "no_task":
		return "无异步任务"
	case "manual_handled":
		return "已人工处理"
	case "ignored":
		return "已忽略"
	default:
		return status
	}
}

func upstreamActionTypeLabel(actionType string) string {
	switch actionType {
	case "instance_renew":
		return "实例续费"
	case "instance_delete", "instance_refund":
		return "资源释放"
	case "instance_suspend":
		return "实例暂停"
	case "instance_unsuspend":
		return "实例恢复"
	case "power-on":
		return "开机"
	case "shutdown":
		return "关机"
	case "hard-shutdown":
		return "强制关机"
	case "reboot":
		return "重启"
	default:
		if strings.HasPrefix(actionType, "plugin_") {
			return "插件动作：" + strings.TrimPrefix(actionType, "plugin_")
		}
		return actionType
	}
}

func pointRewardStatusLabel(status string) string {
	switch status {
	case "pending":
		return "待开通"
	case "claimable":
		return "可领取"
	case "claimed":
		return "已领取"
	case "cancelled":
		return "已取消"
	case "reversed":
		return "已扣回"
	default:
		return status
	}
}

func osLabel(osImage *string) *string {
	if osImage == nil || strings.TrimSpace(*osImage) == "" {
		return nil
	}
	text := strings.TrimSpace(*osImage)
	if strings.Contains(text, "|") {
		parts := strings.SplitN(text, "|", 2)
		label := strings.TrimSpace(parts[1])
		if label != "" {
			return &label
		}
	}
	return &text
}

func pluginLinks(raw JSONRaw) []any {
	payload := jsonMap(raw)
	plugin, _ := payload["provision_plugin"].(map[string]any)
	links, _ := plugin["links"].([]any)
	if links == nil {
		return []any{}
	}
	return links
}

func jsonAnyValue(raw JSONRaw) any {
	if len(raw) == 0 {
		return nil
	}
	var value any
	if err := json.Unmarshal(raw, &value); err != nil {
		return nil
	}
	return value
}

func firstInt(values ...*int) *int {
	for _, value := range values {
		if value != nil {
			return value
		}
	}
	return nil
}

func timeOrNilString(value *time.Time) any {
	if value == nil {
		return nil
	}
	return value.Format(time.RFC3339)
}

func valueOrDefault(value *string, fallback string) string {
	if value == nil || strings.TrimSpace(*value) == "" {
		return fallback
	}
	return *value
}

func generateRenewOrderNo() string {
	return "REN" + time.Now().UTC().Format("20060102150405") + randomDigitsLocal(6)
}

func generateAutoRenewOrderNo() string {
	return "ARN" + time.Now().UTC().Format("20060102150405") + randomDigitsLocal(6)
}

func generateRefundNo() string {
	return "RF" + time.Now().UTC().Format("20060102150405") + randomDigitsLocal(6)
}

func randomDigitsLocal(n int) string {
	buf := make([]byte, n)
	if _, err := rand.Read(buf); err != nil {
		for i := range buf {
			buf[i] = byte(time.Now().UnixNano()>>uint(i%8)) % 10
		}
	}
	for i := range buf {
		buf[i] = '0' + (buf[i] % 10)
	}
	return string(buf)
}
