package commerce

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"net/url"
	"sort"
	"strings"
	"time"

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

func (s *Service) InstanceRealDataHistory(ctx context.Context, userID int64, instanceID int64, query url.Values) ([]map[string]any, error) {
	if _, err := s.userInstance(ctx, userID, instanceID); err != nil {
		return nil, err
	}
	limit := queryInt(query.Get("limit"), 100, 1, 500)
	rows, err := rawRows(s.db.WithContext(ctx).Table("instance_real_data_snapshots").Where("instance_id = ? AND collected_at >= ?", instanceID, time.Now().UTC().Add(-24*time.Hour)).Order("collected_at DESC, id DESC").Limit(limit))
	if err != nil {
		return nil, err
	}
	for left, right := 0, len(rows)-1; left < right; left, right = left+1, right-1 {
		rows[left], rows[right] = rows[right], rows[left]
	}
	return rows, nil
}

func (s *Service) ListInstanceUpgradeProducts(ctx context.Context, userID int64, instanceID int64) ([]map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	var current Product
	if err := s.db.WithContext(ctx).Where("id = ?", instance.ProductID).First(&current).Error; err != nil {
		return nil, err
	}
	var products []Product
	if err := s.db.WithContext(ctx).Where("group_id = ? AND status = ? AND id <> ?", current.GroupID, "active", current.ID).Order("sort_order ASC, id ASC").Find(&products).Error; err != nil {
		return nil, err
	}
	items := make([]map[string]any, 0, len(products))
	for _, product := range products {
		if row, ok := s.upgradeProductPayload(ctx, product, instance.Cycle); ok {
			items = append(items, row)
		}
	}
	sort.Slice(items, func(i, j int) bool {
		left := intValueAny(items[i]["cycle_price"], 0)
		right := intValueAny(items[j]["cycle_price"], 0)
		if left == right {
			return intValueAny(items[i]["id"], 0) < intValueAny(items[j]["id"], 0)
		}
		return left < right
	})
	return items, nil
}

func (s *Service) QuoteInstanceUpgrade(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	var current Product
	if err := s.db.WithContext(ctx).Where("id = ?", instance.ProductID).First(&current).Error; err != nil {
		return nil, err
	}
	targetProductID, _ := int64FromAny(firstNonNil(payload["product_id"], payload["target_product_id"]))
	if targetProductID <= 0 || targetProductID == instance.ProductID {
		return nil, BadRequest("a different target product is required")
	}
	var target Product
	if err := s.db.WithContext(ctx).Where("id = ? AND group_id = ? AND status = ?", targetProductID, current.GroupID, "active").First(&target).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, BadRequest("target product is not available for this instance")
		}
		return nil, err
	}
	currentAmount := instance.UnitPrice
	if currentAmount <= 0 {
		currentAmount = instance.NextRenewAmount
	}
	currentPayload, currentOK := s.upgradeProductPayload(ctx, current, instance.Cycle)
	targetPayload, targetOK := s.upgradeProductPayload(ctx, target, instance.Cycle)
	if !currentOK || !targetOK {
		return nil, BadRequest("current billing cycle is not available for package switching")
	}
	targetAmount := intValueAny(targetPayload["cycle_price"], 0)
	diff := targetAmount - currentAmount
	remainingSeconds, remainingDays, ratio := remainingCycle(&instance)
	charge := 0
	if diff > 0 && remainingSeconds > 0 {
		charge = int(math.Ceil(float64(diff) * ratio))
	}
	diskTotal := s.activeDiskAddonTotal(ctx, instance.ID)
	currentPayload["cycle_price"] = currentAmount
	currentPayload["monthly_price"] = float64(currentAmount) / 100
	return map[string]any{"instance_id": instance.ID, "current_product_id": instance.ProductID, "target_product_id": targetProductID, "current_product": currentPayload, "target_product": targetPayload, "current_unit_price": currentAmount, "new_unit_price": targetAmount, "price_diff_cents": diff, "price_diff_yuan": float64(diff) / 100, "current_charge": charge, "amount_cents": charge, "upgrade_amount": charge, "pay_amount": charge, "remaining_seconds": remainingSeconds, "remaining_days": remainingDays, "new_renew_amount": targetAmount + diskTotal, "disk_addon_amount": diskTotal, "is_upgrade": diff > 0, "is_downgrade": diff < 0}, nil
}

func (s *Service) UpgradeInstance(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (map[string]any, error) {
	quote, err := s.QuoteInstanceUpgrade(ctx, userID, instanceID, payload)
	if err != nil {
		return nil, err
	}
	charge := intValueAny(quote["current_charge"], 0)
	order, balanceAfter, err := s.createAdvancedPaidOrder(ctx, userID, instanceID, "package_upgrade", charge, map[string]any{"quote": quote, "payload": payload})
	if err != nil {
		return nil, err
	}
	resp, err := s.runAuditedInstancePathForOrder(ctx, userID, instanceID, "upgrade", "POST", "upgrade", payload, "user upgrade instance", &order.ID)
	if err != nil {
		if compensateErr := s.compensateAdvancedPaidOrder(ctx, order.ID, err.Error()); compensateErr != nil {
			return paidActionResult(resp, order, balanceAfter, quote, nil), fmt.Errorf("upstream upgrade failed: %w; compensation failed: %v", err, compensateErr)
		}
		return paidActionResult(resp, order, balanceAfter, quote, nil), err
	}
	if resp.Action.Status == "no_task" || resp.Action.Status == "success" {
		if err := s.finalizeAdvancedPaidOrder(ctx, order.ID); err != nil {
			return paidActionResult(resp, order, balanceAfter, quote, nil), err
		}
	}
	return paidActionResult(resp, order, balanceAfter, quote, nil), nil
}

func (s *Service) QuoteInstanceDisk(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	size := intValueAny(firstNonNil(payload["size"], payload["capacity"], payload["disk_size"]), 0)
	if size <= 0 {
		return nil, BadRequest("disk size must be positive")
	}
	pricePerGB := s.settingInt(ctx, "disk.price_per_gb_cents", 100)
	recurring := maxInt(0, size*pricePerGB)
	_, remainingDays, ratio := remainingCycle(&instance)
	charge := int(math.Ceil(float64(recurring) * ratio))
	nextRenew := instance.NextRenewAmount + recurring
	return map[string]any{"instance_id": instance.ID, "size": size, "billable_size": size, "amount_cents": charge, "current_charge": charge, "pay_amount": charge, "recurring_amount": recurring, "next_renew_amount": nextRenew, "remaining_days": remainingDays, "cycle": instance.Cycle, "charged_until": timeOrNilString(instance.ExpiresAt), "expires_at": timeOrNilString(instance.ExpiresAt), "unit_price_cents": pricePerGB}, nil
}

func (s *Service) CreateInstanceDisk(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (map[string]any, error) {
	quote, err := s.QuoteInstanceDisk(ctx, userID, instanceID, payload)
	if err != nil {
		return nil, err
	}
	charge := intValueAny(quote["current_charge"], 0)
	order, balanceAfter, err := s.createAdvancedPaidOrder(ctx, userID, instanceID, "disk_addon", charge, map[string]any{"quote": quote, "payload": payload})
	if err != nil {
		return nil, err
	}
	if err := s.createDiskAddonRecord(ctx, userID, instanceID, order.ID, "create_disk", quote, payload, nil); err != nil {
		if compensateErr := s.compensateAdvancedPaidOrder(ctx, order.ID, err.Error()); compensateErr != nil {
			return nil, fmt.Errorf("create disk billing record failed: %w; compensation failed: %v", err, compensateErr)
		}
		return nil, err
	}
	resp, err := s.runAuditedInstancePathForOrder(ctx, userID, instanceID, "disk_create", "POST", "disks", payload, "user create disk", &order.ID)
	if err != nil {
		if compensateErr := s.compensateAdvancedPaidOrder(ctx, order.ID, err.Error()); compensateErr != nil {
			return paidActionResult(resp, order, balanceAfter, quote, nil), fmt.Errorf("upstream disk creation failed: %w; compensation failed: %v", err, compensateErr)
		}
		return paidActionResult(resp, order, balanceAfter, quote, nil), err
	}
	if err := s.updateDiskAddonTask(ctx, order.ID, resp.TaskID); err != nil {
		return paidActionResult(resp, order, balanceAfter, quote, nil), err
	}
	if resp.Action.Status == "no_task" || resp.Action.Status == "success" {
		if err := s.finalizeAdvancedPaidOrder(ctx, order.ID); err != nil {
			return paidActionResult(resp, order, balanceAfter, quote, nil), err
		}
	}
	return paidActionResult(resp, order, balanceAfter, quote, nil), nil
}

func (s *Service) QuoteResizeInstanceDisk(ctx context.Context, userID int64, instanceID int64, diskID int64, payload map[string]any) (map[string]any, error) {
	if _, err := s.userInstance(ctx, userID, instanceID); err != nil {
		return nil, err
	}
	size := intValueAny(firstNonNil(payload["size"], payload["capacity"], payload["disk_size"]), 0)
	currentSize := intValueAny(firstNonNil(payload["current_size"], payload["source_size"]), 0)
	delta := size
	if currentSize > 0 {
		delta = size - currentSize
	}
	if delta <= 0 {
		return nil, BadRequest("target disk size must be greater than current size")
	}
	pricePerGB := s.settingInt(ctx, "disk.price_per_gb_cents", 100)
	recurring := maxInt(0, delta*pricePerGB)
	instance, _ := s.userInstance(ctx, userID, instanceID)
	_, remainingDays, ratio := remainingCycle(&instance)
	charge := int(math.Ceil(float64(recurring) * ratio))
	nextRenew := instance.NextRenewAmount + recurring
	return map[string]any{"instance_id": instanceID, "disk_id": diskID, "diskid": diskID, "size": size, "billable_size": delta, "current_size": currentSize, "source_size": currentSize, "target_size": size, "delta_size": delta, "amount_cents": charge, "current_charge": charge, "pay_amount": charge, "recurring_amount": recurring, "next_renew_amount": nextRenew, "remaining_days": remainingDays, "cycle": instance.Cycle, "charged_until": timeOrNilString(instance.ExpiresAt), "expires_at": timeOrNilString(instance.ExpiresAt), "unit_price_cents": pricePerGB}, nil
}

func (s *Service) ResizeInstanceDisk(ctx context.Context, userID int64, instanceID int64, diskID int64, payload map[string]any) (map[string]any, error) {
	quote, err := s.QuoteResizeInstanceDisk(ctx, userID, instanceID, diskID, payload)
	if err != nil {
		return nil, err
	}
	charge := intValueAny(quote["current_charge"], 0)
	order, balanceAfter, err := s.createAdvancedPaidOrder(ctx, userID, instanceID, "disk_resize", charge, map[string]any{"quote": quote, "payload": payload})
	if err != nil {
		return nil, err
	}
	if err := s.createDiskAddonRecord(ctx, userID, instanceID, order.ID, "resize_disk", quote, payload, nil); err != nil {
		if compensateErr := s.compensateAdvancedPaidOrder(ctx, order.ID, err.Error()); compensateErr != nil {
			return nil, fmt.Errorf("create disk resize billing record failed: %w; compensation failed: %v", err, compensateErr)
		}
		return nil, err
	}
	resp, err := s.submitDiskActionForOrder(ctx, userID, instanceID, diskID, "disk_resize", "PUT", fmt.Sprintf("disks/%d/resize", diskID), payload, "user resize disk", &order.ID)
	if err != nil {
		if compensateErr := s.compensateAdvancedPaidOrder(ctx, order.ID, err.Error()); compensateErr != nil {
			return paidActionResult(resp, order, balanceAfter, quote, &diskID), fmt.Errorf("upstream disk resize failed: %w; compensation failed: %v", err, compensateErr)
		}
		return paidActionResult(resp, order, balanceAfter, quote, &diskID), err
	}
	if err := s.updateDiskAddonTask(ctx, order.ID, resp.TaskID); err != nil {
		return paidActionResult(resp, order, balanceAfter, quote, &diskID), err
	}
	if resp.Action.Status == "no_task" || resp.Action.Status == "success" {
		if err := s.finalizeAdvancedPaidOrder(ctx, order.ID); err != nil {
			return paidActionResult(resp, order, balanceAfter, quote, &diskID), err
		}
	}
	return paidActionResult(resp, order, balanceAfter, quote, &diskID), nil
}

func (s *Service) MountInstanceDisk(ctx context.Context, userID int64, instanceID int64, diskID int64, payload map[string]any) (InstanceActionResponse, error) {
	return s.submitDiskAction(ctx, userID, instanceID, diskID, "disk_mount", "POST", fmt.Sprintf("disks/%d/mount", diskID), payload, "user mount disk")
}

func (s *Service) DetachInstanceDisk(ctx context.Context, userID int64, instanceID int64, diskID int64) (InstanceActionResponse, error) {
	return s.submitDiskAction(ctx, userID, instanceID, diskID, "disk_detach", "DELETE", fmt.Sprintf("disks/%d/detach", diskID), nil, "user detach disk")
}

func (s *Service) DeleteInstanceDisk(ctx context.Context, userID int64, instanceID int64, diskID int64) (InstanceActionResponse, error) {
	return s.submitDiskAction(ctx, userID, instanceID, diskID, "disk_delete", "DELETE", fmt.Sprintf("disks/%d", diskID), nil, "user delete disk")
}

func (s *Service) SuspendInstance(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (InstanceActionResponse, error) {
	resp, err := s.runAuditedInstancePath(ctx, userID, instanceID, "suspend", "POST", "suspend", payload, "user suspend instance")
	if err == nil {
		_ = s.db.WithContext(ctx).Model(&Instance{}).Where("id = ? AND user_id = ?", instanceID, userID).Updates(map[string]any{"status": "suspended", "suspend_type": "user"}).Error
	}
	return resp, err
}

func (s *Service) UnsuspendInstance(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (InstanceActionResponse, error) {
	resp, err := s.runAuditedInstancePath(ctx, userID, instanceID, "unsuspend", "POST", "unsuspend", payload, "user unsuspend instance")
	if err == nil {
		_ = s.db.WithContext(ctx).Model(&Instance{}).Where("id = ? AND user_id = ?", instanceID, userID).Updates(map[string]any{"status": "active", "suspend_type": nil}).Error
	}
	return resp, err
}

func (s *Service) ClaimInstancePoints(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	var out map[string]any
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := s.withDB(tx)
		var rewards []PointReward
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id = ? AND instance_id = ? AND status = ?", userID, instanceID, "claimable").Order("id ASC").Find(&rewards).Error; err != nil {
			return err
		}
		if len(rewards) == 0 {
			out = map[string]any{"claimed_points": 0, "claimed_count": 0, "pending_count": 0}
			return nil
		}
		now := time.Now().UTC()
		claimedPoints := 0
		availablePoints := 0
		for _, reward := range rewards {
			refNo := valueOrDefault(reward.RefNo, fmt.Sprintf("reward:%d", reward.ID))
			log, err := txService.addPoints(ctx, userID, reward.Points, "self_purchase_reward", reward.OrderID, refNo, valueOrDefault(reward.Remark, "claim self purchase reward"))
			if err != nil {
				return err
			}
			if err := tx.Model(&PointReward{}).Where("id = ? AND status = ?", reward.ID, "claimable").Updates(map[string]any{"status": "claimed", "claimed_at": now, "point_log_id": log.ID}).Error; err != nil {
				return err
			}
			if err := tx.Model(&Order{}).Where("id = ?", reward.OrderID).Updates(map[string]any{"points_claimed": true, "points_claimed_at": now, "points_claimed_amount": gorm.Expr("points_claimed_amount + ?", reward.Points)}).Error; err != nil {
				return err
			}
			claimedPoints += reward.Points
			availablePoints = log.BalanceAfter
		}
		var pendingCount int64
		if err := tx.Model(&PointReward{}).Where("user_id = ? AND instance_id = ? AND status = ?", userID, instanceID, "claimable").Count(&pendingCount).Error; err != nil {
			return err
		}
		out = map[string]any{"claimed_points": claimedPoints, "claimed_count": len(rewards), "pending_count": pendingCount, "available_points": availablePoints, "claimed_at": now}
		return nil
	})
	return out, err
}

func (s *Service) submitDiskAction(ctx context.Context, userID int64, instanceID int64, diskID int64, action string, method string, suffix string, payload any, remark string) (InstanceActionResponse, error) {
	return s.submitDiskActionForOrder(ctx, userID, instanceID, diskID, action, method, suffix, payload, remark, nil)
}

func (s *Service) submitDiskActionForOrder(ctx context.Context, userID int64, instanceID int64, diskID int64, action string, method string, suffix string, payload any, remark string, orderID *int64) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/clouds/%d/%s", *instance.UpstreamID, suffix)
	return s.submitInstanceAdvancedActionForOrder(ctx, instance, action, method, path, payload, remark, orderID)
}

func (s *Service) productCyclePrice(ctx context.Context, productID int64, cycle string, fallback int) int {
	var pricing ProductPricing
	if err := s.db.WithContext(ctx).Where("product_id = ? AND cycle = ? AND is_enabled = ?", productID, cycle, true).First(&pricing).Error; err == nil {
		return yuanToCents(pricing.Price)
	}
	return fallback
}

func (s *Service) upgradeProductPayload(ctx context.Context, product Product, cycle string) (map[string]any, bool) {
	var pricing ProductPricing
	if err := s.db.WithContext(ctx).Where("product_id = ? AND cycle = ? AND is_enabled = ?", product.ID, cycle, true).First(&pricing).Error; err != nil {
		return nil, false
	}
	price := yuanToCents(pricing.Price)
	return map[string]any{
		"id":                product.ID,
		"name":              product.Name,
		"description":       nil,
		"cycle":             cycle,
		"cycle_price":       price,
		"monthly_price":     float64(price) / 100,
		"cpu":               product.VCPU,
		"memory":            parseMemoryMB(product.Memory),
		"bandwidth":         parseProductDisplayInt(product.Bandwidth),
		"in_bw":             nil,
		"out_bw":            nil,
		"traffic_quota":     nil,
		"traffic_type":      nil,
		"nat_acl_limit":     nil,
		"nat_web_limit":     nil,
		"memory_display":    product.Memory,
		"disk_display":      product.Disk,
		"bandwidth_display": product.Bandwidth,
	}, true
}

func parseProductDisplayInt(value *string) *int {
	if value == nil {
		return nil
	}
	text := strings.TrimSpace(*value)
	if text == "" {
		return nil
	}
	var parsed int
	if _, err := fmt.Sscan(text, &parsed); err != nil {
		return nil
	}
	return &parsed
}

func paidActionResult(resp InstanceActionResponse, order *Order, balanceAfter int, quote map[string]any, diskID *int64) map[string]any {
	charged := intValueAny(quote["current_charge"], 0)
	result := map[string]any{
		"action":            resp.Action,
		"status":            resp.Action.Status,
		"taskid":            resp.TaskID,
		"instance_id":       quote["instance_id"],
		"charged":           charged,
		"amount":            charged,
		"balance":           balanceAfter,
		"recurring_amount":  intValueAny(quote["recurring_amount"], 0),
		"new_unit_price":    intValueAny(quote["new_unit_price"], 0),
		"next_renew_amount": intValueAny(quote["new_renew_amount"], 0),
		"new_renew_amount":  intValueAny(quote["new_renew_amount"], 0),
		"is_downgrade":      quote["is_downgrade"] == true,
		"source_size":       intValueAny(firstNonNil(quote["source_size"], quote["current_size"]), 0),
		"target_size":       intValueAny(firstNonNil(quote["target_size"], quote["size"]), 0),
		"delta_size":        intValueAny(quote["delta_size"], 0),
	}
	if order != nil {
		result["order_id"] = order.ID
		result["order_no"] = order.OutOrderNo
	}
	if diskID != nil {
		result["diskid"] = *diskID
	} else if payload, ok := resp.Action.ResponsePayload.(map[string]any); ok {
		result["diskid"] = firstNonNil(payload["diskid"], payload["disk_id"])
	}
	result["billing"] = map[string]any{
		"action_type":     resp.Action.ActionType,
		"amount_cents":    charged,
		"balance_cents":   balanceAfter,
		"order_id":        result["order_id"],
		"order_no":        result["order_no"],
		"recurring_cents": result["recurring_amount"],
	}
	return result
}

func remainingCycle(instance *Instance) (int64, int, float64) {
	if instance == nil || instance.ExpiresAt == nil || !instance.ExpiresAt.After(time.Now().UTC()) {
		return 0, 0, 0
	}
	now := time.Now().UTC()
	remaining := int64(instance.ExpiresAt.Sub(now).Seconds())
	period := int64(30 * 24 * 3600)
	switch strings.ToLower(instance.Cycle) {
	case "year", "annually", "annual", "yearly":
		period = 365 * 24 * 3600
	case "quarter", "quarterly":
		period = 90 * 24 * 3600
	case "semiannual", "half_year":
		period = 180 * 24 * 3600
	case "day", "daily":
		period = 24 * 3600
	}
	ratio := float64(remaining) / float64(period)
	if ratio > 1 {
		ratio = 1
	}
	return remaining, int(math.Ceil(float64(remaining) / 86400.0)), ratio
}

func (s *Service) activeDiskAddonTotal(ctx context.Context, instanceID int64) int {
	var total int64
	_ = s.db.WithContext(ctx).Table("instance_disk_addons").Where("instance_id = ? AND status = ?", instanceID, "active").Select("COALESCE(SUM(recurring_amount),0)").Scan(&total).Error
	return int(total)
}

func (s *Service) createAdvancedPaidOrder(ctx context.Context, userID int64, instanceID int64, itemType string, amount int, snapshot map[string]any) (*Order, int, error) {
	if amount < 0 {
		return nil, 0, BadRequest("advanced action amount cannot be negative")
	}
	var out Order
	balanceAfter := 0
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := &Service{db: tx, redis: s.redis, logger: s.logger, plugins: s.plugins}
		var user UserAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", userID).First(&user).Error; err != nil {
			return err
		}
		if user.Balance < amount {
			return HTTPError{Status: 402, Detail: "balance not enough"}
		}
		orderNo := generateOrderNo()
		items := []map[string]any{{"type": itemType, "instance_id": instanceID, "amount_cents": amount, "detail": snapshot}}
		order := Order{UserID: userID, OutOrderNo: orderNo, Status: "paid", TotalAmount: amount, DiscountAmount: 0, ItemsSnapshot: jsonRawFromAny(items), Remark: stringPtr(itemType)}
		if err := tx.Create(&order).Error; err != nil {
			return err
		}
		balanceAfter = user.Balance
		remark := fmt.Sprintf("%s %s", itemType, orderNo)
		if amount > 0 {
			balanceAfter = user.Balance - amount
			if err := tx.Model(&UserAccount{}).Where("id = ?", userID).Update("balance", balanceAfter).Error; err != nil {
				return err
			}
			if err := tx.Create(&BalanceLog{UserID: userID, ChangeType: "order_pay", Delta: -amount, BalanceAfter: balanceAfter, RefNo: &orderNo, Remark: &remark}).Error; err != nil {
				return err
			}
			if err := txService.calculateOrderSettlement(ctx, &order, userID, amount); err != nil {
				return err
			}
			if err := txService.freezeAgentSubsidyForOrder(ctx, order); err != nil {
				return err
			}
			if err := txService.createSelfPurchasePointReward(ctx, userID, order.ID, orderNo, instanceID, amount, itemType, "claimable"); err != nil {
				return err
			}
			eventKey := orderNo + ":" + itemType
			if err := tx.Create(&UserSpendEvent{UserID: userID, EventType: itemType, EventKey: eventKey, AmountCents: amount, OrderID: &order.ID, RefNo: &orderNo, Remark: &remark, PayloadSnapshot: jsonRawFromAny(snapshot)}).Error; err != nil {
				return err
			}
		}
		out = order
		return nil
	})
	if err != nil {
		return nil, 0, err
	}
	if amount > 0 {
		if _, err := s.SettleOrder(ctx, out.ID, true); err != nil {
			if compensateErr := s.compensateAdvancedPaidOrder(ctx, out.ID, err.Error()); compensateErr != nil {
				return nil, 0, fmt.Errorf("settle advanced action order: %w; compensation failed: %v", err, compensateErr)
			}
			return nil, 0, err
		}
	}
	return &out, balanceAfter, nil
}

func (s *Service) createDiskAddonRecord(ctx context.Context, userID int64, instanceID int64, orderID int64, kind string, quote map[string]any, payload map[string]any, taskID *int64) error {
	now := time.Now().UTC()
	row := map[string]any{
		"user_id":               userID,
		"instance_id":           instanceID,
		"order_id":              orderID,
		"kind":                  kind,
		"size_gb":               intValueAny(firstNonNil(quote["size"], quote["delta_size"]), 0),
		"source_size_gb":        nullableIntAny(quote["current_size"]),
		"target_size_gb":        nullableIntAny(quote["size"]),
		"delta_size_gb":         nullableIntAny(quote["delta_size"]),
		"store_id":              intValueAny(payload["store_id"], 0),
		"driver":                nullStringAny(payload["driver"]),
		"cache":                 nullStringAny(payload["cache"]),
		"io":                    nullStringAny(payload["io"]),
		"iops_min":              intValueAny(payload["iops_min"], 0),
		"iops_max":              intValueAny(payload["iops_max"], 0),
		"cycle":                 valueOrDefaultString(payload["cycle"], "month"),
		"current_charge_amount": intValueAny(quote["current_charge"], 0),
		"recurring_amount":      intValueAny(quote["recurring_amount"], 0),
		"charged_until":         nil,
		"status":                "pending",
		"payload_snapshot":      jsonRawFromAny(map[string]any{"quote": quote, "payload": payload}),
		"activated_at":          nil,
		"created_at":            now,
		"updated_at":            now,
	}
	if taskID != nil {
		row["upstream_task_id"] = *taskID
	}
	if diskID, ok := int64FromAny(firstNonNil(payload["disk_id"], payload["upstream_disk_id"])); ok {
		row["upstream_disk_id"] = diskID
	}
	return s.db.WithContext(ctx).Table("instance_disk_addons").Create(row).Error
}

func (s *Service) updateDiskAddonTask(ctx context.Context, orderID int64, taskID *int64) error {
	if taskID == nil {
		return nil
	}
	return s.db.WithContext(ctx).Table("instance_disk_addons").Where("order_id = ?", orderID).Update("upstream_task_id", *taskID).Error
}

func (s *Service) finalizeAdvancedPaidOrder(ctx context.Context, orderID int64) error {
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return (&Service{db: tx, redis: s.redis, logger: s.logger, plugins: s.plugins, identity: s.identity}).finalizeAdvancedPaidOrderTx(ctx, orderID)
	})
}

func (s *Service) finalizeAdvancedPaidOrderTx(ctx context.Context, orderID int64) error {
	var order Order
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", orderID).First(&order).Error; err != nil {
		return err
	}
	if order.Status == "refunded" {
		return Conflict("advanced action order was already refunded")
	}
	if order.Status != "paid" {
		return Conflict("advanced action order is not paid")
	}
	kind, quote, err := advancedOrderSnapshot(order)
	if err != nil {
		return err
	}
	instanceID, ok := int64FromAny(quote["instance_id"])
	if !ok || instanceID <= 0 {
		return fmt.Errorf("advanced action order %d has no instance id", order.ID)
	}
	var instance Instance
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND user_id = ?", instanceID, order.UserID).First(&instance).Error; err != nil {
		return err
	}
	now := time.Now().UTC()
	switch kind {
	case "package_upgrade":
		updates := map[string]any{}
		if productID, ok := int64FromAny(quote["target_product_id"]); ok && productID > 0 {
			updates["product_id"] = productID
		}
		if unit := intValueAny(quote["new_unit_price"], 0); unit > 0 {
			updates["unit_price"] = unit
		}
		if unit := intValueAny(quote["new_unit_price"], 0); unit > 0 {
			updates["next_renew_amount"] = unit + s.activeDiskAddonTotal(ctx, instance.ID)
		}
		if len(updates) == 0 {
			return fmt.Errorf("advanced action order %d has no upgrade target", order.ID)
		}
		return s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", instance.ID).Updates(updates).Error
	case "disk_addon", "disk_resize":
		result := s.db.WithContext(ctx).Table("instance_disk_addons").Where("order_id = ? AND status = ?", order.ID, "pending").Updates(map[string]any{"status": "active", "activated_at": now, "updated_at": now})
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return nil
		}
		recurring := intValueAny(quote["recurring_amount"], 0)
		if recurring < 0 {
			return fmt.Errorf("advanced action order %d has invalid recurring amount", order.ID)
		}
		if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", instance.ID).Update("next_renew_amount", gorm.Expr("next_renew_amount + ?", recurring)).Error; err != nil {
			return err
		}
		return nil
	default:
		return fmt.Errorf("order %d is not an advanced paid action", order.ID)
	}
}

func (s *Service) compensateAdvancedPaidOrder(ctx context.Context, orderID int64, failure string) error {
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return (&Service{db: tx, redis: s.redis, logger: s.logger, plugins: s.plugins, identity: s.identity}).compensateAdvancedPaidOrderTx(ctx, orderID, failure)
	})
}

func (s *Service) compensateAdvancedPaidOrderTx(ctx context.Context, orderID int64, failure string) error {
	var order Order
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", orderID).First(&order).Error; err != nil {
		return err
	}
	kind, snapshot, err := compensatablePaidOrderSnapshot(order)
	if err != nil {
		return err
	}
	if kind != "package_upgrade" && kind != "disk_addon" && kind != "disk_resize" && kind != "renewal" {
		return fmt.Errorf("order %d is not compensatable advanced action", order.ID)
	}
	if order.Status == "refunded" {
		return nil
	}
	if order.Status != "paid" {
		return fmt.Errorf("cannot compensate order %d in status %s", order.ID, order.Status)
	}
	var user UserAccount
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", order.UserID).First(&user).Error; err != nil {
		return err
	}
	reason := "upstream action failed"
	if strings.TrimSpace(failure) != "" {
		reason += ": " + strings.TrimSpace(failure)
	}
	now := time.Now().UTC()
	refundNo := generateRefundNo()
	amount := maxInt(0, order.TotalAmount-order.DiscountAmount)
	refund := OrderRefund{RefundNo: refundNo, UserID: order.UserID, OrderID: order.ID, RefundType: "upstream_failure", RefundAmount: amount, Status: "success", ResourceStatus: "failed", ResourceError: &reason, ResourceCompletedAt: &now, Reason: &reason, PayloadSnapshot: jsonRawFromAny(map[string]any{"order_id": order.ID, "order_no": order.OutOrderNo, "action_type": kind, "failure": failure, "compensated_by": "go-server"})}
	if err := s.db.WithContext(ctx).Create(&refund).Error; err != nil {
		return err
	}
	if amount > 0 {
		balanceAfter := user.Balance + amount
		if err := s.db.WithContext(ctx).Model(&UserAccount{}).Where("id = ?", user.ID).Update("balance", balanceAfter).Error; err != nil {
			return err
		}
		if err := s.db.WithContext(ctx).Create(&BalanceLog{UserID: user.ID, ChangeType: "order_refund", Delta: amount, BalanceAfter: balanceAfter, RefNo: &refundNo, Remark: &reason}).Error; err != nil {
			return err
		}
	}
	if err := s.reverseSpendEventsForOrder(ctx, order.UserID, order.ID, refundNo, reason, 10000); err != nil {
		return err
	}
	if err := s.refundUsedPointsForOrder(ctx, order.UserID, order.ID, order.OutOrderNo, 10000, reason); err != nil {
		return err
	}
	if err := s.reverseOrderPointRewards(ctx, order.ID, reason); err != nil {
		return err
	}
	if err := s.reverseOrderSettlements(ctx, &order, reason); err != nil {
		return err
	}
	order.Status = "refunded"
	order.Remark = &reason
	if err := s.db.WithContext(ctx).Save(&order).Error; err != nil {
		return err
	}
	if kind == "disk_addon" || kind == "disk_resize" {
		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 err
		}
	} else if kind == "renewal" {
		instanceID, ok := int64FromAny(snapshot["instance_id"])
		if !ok || instanceID <= 0 {
			return fmt.Errorf("renewal order %d has no instance id", order.ID)
		}
		updates := map[string]any{
			"expires_at":        timeValueAny(snapshot["old_expires_at"]),
			"next_renew_amount": intValueAny(snapshot["old_next_renew_amount"], 0),
		}
		if oldStatus, ok := stringFromAny(snapshot["old_status"]); ok && strings.TrimSpace(oldStatus) != "" {
			updates["status"] = oldStatus
		}
		if oldSuspendType, ok := stringFromAny(snapshot["old_suspend_type"]); ok && strings.TrimSpace(oldSuspendType) != "" {
			updates["suspend_type"] = oldSuspendType
		} else {
			updates["suspend_type"] = nil
		}
		if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ? AND user_id = ?", instanceID, order.UserID).Updates(updates).Error; err != nil {
			return err
		}
		if err := s.db.WithContext(ctx).Model(&AutoRenewalEvent{}).Where("order_id = ?", order.ID).Updates(map[string]any{"status": "failed", "error_code": "upstream_failure", "message": reason, "processed_at": now}).Error; err != nil {
			return err
		}
	}
	return nil
}

func advancedOrderSnapshot(order Order) (string, map[string]any, error) {
	kind, snapshot, err := compensatablePaidOrderSnapshot(order)
	if err != nil {
		return "", nil, err
	}
	if kind == "renewal" {
		return "", nil, fmt.Errorf("order %d is a renewal, not an advanced action", order.ID)
	}
	return kind, snapshot, nil
}

func compensatablePaidOrderSnapshot(order Order) (string, map[string]any, error) {
	var items []map[string]any
	if err := json.Unmarshal(order.ItemsSnapshot, &items); err != nil {
		return "", nil, fmt.Errorf("decode advanced action order %d: %w", order.ID, err)
	}
	if len(items) != 1 {
		return "", nil, fmt.Errorf("advanced action order %d has invalid item count", order.ID)
	}
	kind, _ := stringFromAny(items[0]["type"])
	if kind == "renewal" {
		return kind, items[0], nil
	}
	detail, ok := items[0]["detail"].(map[string]any)
	if !ok {
		return "", nil, fmt.Errorf("advanced action order %d has no detail", order.ID)
	}
	quote, ok := detail["quote"].(map[string]any)
	if !ok {
		return "", nil, fmt.Errorf("advanced action order %d has no quote", order.ID)
	}
	return kind, quote, nil
}

func timeValueAny(value any) any {
	text, ok := stringFromAny(value)
	if !ok || strings.TrimSpace(text) == "" {
		return nil
	}
	parsed, err := time.Parse(time.RFC3339, text)
	if err != nil {
		return nil
	}
	return parsed.UTC()
}

func nullableIntAny(value any) any {
	if parsed, ok := int64FromAny(value); ok && parsed > 0 {
		return int(parsed)
	}
	return nil
}

func nullStringAny(value any) any {
	text := strings.TrimSpace(fmt.Sprint(value))
	if text == "" || text == "<nil>" {
		return nil
	}
	return text
}

func valueOrDefaultString(value any, fallback string) string {
	text := strings.TrimSpace(fmt.Sprint(value))
	if text == "" || text == "<nil>" {
		return fallback
	}
	return text
}

func actionResultMap(value any) map[string]any {
	if value == nil {
		return map[string]any{}
	}
	if out, ok := value.(map[string]any); ok {
		return out
	}
	return map[string]any{"upstream": value}
}
