package commerce

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

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

	"qycw/go-server/internal/plugins"
)

const unsupportedProviderMessage = "Go upstream provider is not implemented and no enabled plugin handled this action"

var powerActionPaths = map[string]string{
	"power-on":      "/on",
	"shutdown":      "/off",
	"hard-shutdown": "/hardoff",
	"reboot":        "/reboot",
}

type provisionManageResponse struct {
	OK        bool           `json:"ok"`
	Message   string         `json:"message"`
	Retryable bool           `json:"retryable"`
	Result    map[string]any `json:"result"`
}

func (s *Service) ListInstanceActions(ctx context.Context, userID int64, instanceID int64) ([]UpstreamActionLogResponse, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	if _, err := s.userInstance(ctx, userID, instanceID); err != nil {
		return nil, err
	}
	var logs []UpstreamActionLog
	if err := s.db.WithContext(ctx).
		Where("instance_id = ?", instanceID).
		Order("id DESC").
		Find(&logs).Error; err != nil {
		return nil, err
	}
	out := make([]UpstreamActionLogResponse, 0, len(logs))
	for _, item := range logs {
		out = append(out, newUpstreamActionLogResponse(item))
	}
	return out, nil
}

func (s *Service) RunPowerAction(ctx context.Context, userID int64, instanceID int64, action string) (InstanceActionResponse, error) {
	if err := s.requireDB(); err != nil {
		return InstanceActionResponse{}, err
	}
	path, ok := powerActionPaths[action]
	if !ok {
		return InstanceActionResponse{}, BadRequest("不支持的实例动作")
	}
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("实例尚未绑定上游资源，无法提交电源动作")
	}
	log, err := s.submitUpstreamActionWithAudit(ctx, instance, action, "user", nil, nil, "POST", fmt.Sprintf("/clouds/%d%s", *instance.UpstreamID, path), nil, "用户实例电源动作")
	if err != nil {
		return InstanceActionResponse{Action: newUpstreamActionLogResponse(log)}, err
	}
	response := InstanceActionResponse{
		Action: newUpstreamActionLogResponse(log),
		TaskID: log.UpstreamTaskID,
		Result: map[string]any{
			"status":  log.Status,
			"taskid":  log.UpstreamTaskID,
			"message": "指令已下发",
		},
	}
	return response, nil
}

func (s *Service) RunPluginManageAction(ctx context.Context, userID int64, instanceID int64, action string, payload map[string]any) (InstanceActionResponse, error) {
	if err := s.requireDB(); err != nil {
		return InstanceActionResponse{}, err
	}
	action = strings.TrimSpace(action)
	if action == "" {
		return InstanceActionResponse{}, BadRequest("插件动作不能为空")
	}
	if payload == nil {
		payload = map[string]any{}
	}
	var out InstanceActionResponse
	var actionErr error
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := &Service{db: tx, logger: s.logger, plugins: s.plugins}
		var instance Instance
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND user_id = ?", instanceID, userID).
			First(&instance).Error; err != nil {
			if errorsIsRecordNotFound(err) {
				return NotFound("实例不存在")
			}
			return err
		}
		if instance.Status != "active" && instance.Status != "suspended" && instance.Status != "provision_failed" {
			return BadRequest("当前实例状态不允许执行插件管理动作")
		}
		var product Product
		if err := tx.Where("id = ?", instance.ProductID).First(&product).Error; err != nil {
			if errorsIsRecordNotFound(err) {
				return BadRequest("实例商品不存在")
			}
			return err
		}
		if product.APIType != "plugin" || product.APIIdentifier == nil || strings.TrimSpace(*product.APIIdentifier) == "" {
			return BadRequest("该实例不是插件开通产品")
		}
		pluginID := strings.TrimSpace(*product.APIIdentifier)
		manifest := txService.publicPluginForProduct(product)
		if manifest == nil {
			return BadRequest("插件配置不可用")
		}
		if !pluginActionAllowed(manifest, action) {
			return BadRequest(fmt.Sprintf("插件未声明管理动作：%s", action))
		}
		if pluginActionAudience(manifest, action) == "admin" {
			return BadRequest("该操作仅限管理员执行")
		}
		bridgeResponse, bridgeErr := txService.callProvisionManage(ctx, tx, instance, product, pluginID, action, payload)
		now := time.Now().UTC()
		status := "success"
		var taskID *int64
		var errMessage *string
		responsePayload := map[string]any{}
		if bridgeErr != nil {
			status = "failed"
			message := bridgeErr.Error()
			errMessage = &message
			responsePayload["message"] = message
		} else if !bridgeResponse.OK {
			status = "failed"
			message := strings.TrimSpace(bridgeResponse.Message)
			if message == "" {
				message = "插件管理动作执行失败"
			}
			errMessage = &message
			responsePayload["message"] = message
			responsePayload["retryable"] = bridgeResponse.Retryable
		} else {
			responsePayload = sanitizePluginManageResult(bridgeResponse.Result)
			outcomeTaskID, message := pluginManageResultOutcome(bridgeResponse.Result)
			taskID = outcomeTaskID
			if message != "" {
				status = "failed"
				errMessage = &message
			}
		}
		completedAt := &now
		method := "PLUGIN"
		path := action
		remark := fmt.Sprintf("plugin %s action %s", pluginID, action)
		log := UpstreamActionLog{
			UserID:          &instance.UserID,
			OrderID:         &instance.OrderID,
			InstanceID:      &instance.ID,
			CloudServerID:   instance.CloudServerID,
			UpstreamID:      instance.UpstreamID,
			ActionType:      "plugin_" + action,
			ResourceType:    "instance",
			ResourceID:      resourceIDForInstance(instance),
			Source:          "user",
			Status:          status,
			UpstreamTaskID:  taskID,
			RequestMethod:   &method,
			RequestPath:     &path,
			RequestPayload:  jsonRawFromAny(payload),
			ResponsePayload: jsonRawFromAny(responsePayload),
			ErrorMessage:    errMessage,
			Remark:          &remark,
			CompletedAt:     completedAt,
		}
		log, err := txService.createUpstreamActionLog(ctx, log)
		if err != nil {
			return err
		}
		if bridgeErr == nil && bridgeResponse.OK && status != "failed" {
			if err := applyPluginManageResult(tx, &instance, action, bridgeResponse.Result, now); err != nil {
				return err
			}
		}
		out = InstanceActionResponse{
			Action: newUpstreamActionLogResponse(log),
			TaskID: taskID,
			Result: responsePayload,
		}
		if errMessage != nil {
			actionErr = BadRequest(*errMessage)
			out.Result = map[string]any{
				"status":  status,
				"message": *errMessage,
			}
		}
		return nil
	})
	if err != nil {
		return out, err
	}
	return out, actionErr
}

func newUpstreamActionLogResponse(item UpstreamActionLog) UpstreamActionLogResponse {
	return UpstreamActionLogResponse{
		ID:              item.ID,
		UserID:          item.UserID,
		OrderID:         item.OrderID,
		OrderRefundID:   item.OrderRefundID,
		InstanceID:      item.InstanceID,
		CloudServerID:   item.CloudServerID,
		UpstreamID:      item.UpstreamID,
		ActionType:      item.ActionType,
		ResourceType:    item.ResourceType,
		ResourceID:      item.ResourceID,
		Source:          item.Source,
		Status:          item.Status,
		UpstreamTaskID:  item.UpstreamTaskID,
		RequestMethod:   item.RequestMethod,
		RequestPath:     item.RequestPath,
		RequestPayload:  jsonAnyValue(item.RequestPayload),
		ResponsePayload: jsonAnyValue(item.ResponsePayload),
		TaskPayload:     jsonAnyValue(item.TaskPayload),
		ErrorMessage:    item.ErrorMessage,
		Remark:          item.Remark,
		AttemptCount:    item.AttemptCount,
		LastCheckedAt:   item.LastCheckedAt,
		CompletedAt:     item.CompletedAt,
		CreatedAt:       item.CreatedAt,
		UpdatedAt:       item.UpdatedAt,
	}
}

func (s *Service) recordUnsupportedResourceAction(ctx context.Context, instance Instance, actionType string, source string, orderID *int64, refundID *int64, method string, path string, requestPayload any, message string) (UpstreamActionLog, error) {
	now := time.Now().UTC()
	if strings.TrimSpace(message) == "" {
		message = unsupportedProviderMessage
	}
	resourceID := resourceIDForInstance(instance)
	log := UpstreamActionLog{
		UserID:         &instance.UserID,
		OrderID:        orderID,
		OrderRefundID:  refundID,
		InstanceID:     &instance.ID,
		CloudServerID:  instance.CloudServerID,
		UpstreamID:     instance.UpstreamID,
		ActionType:     actionType,
		ResourceType:   "instance",
		ResourceID:     resourceID,
		Source:         source,
		Status:         "failed",
		RequestMethod:  stringPtr(method),
		RequestPath:    stringPtr(path),
		RequestPayload: jsonRawFromAny(requestPayload),
		ErrorMessage:   &message,
		Remark:         &message,
		CompletedAt:    &now,
	}
	created, err := s.createUpstreamActionLog(ctx, log)
	if err != nil {
		return UpstreamActionLog{}, err
	}
	return created, BadRequest(message)
}

func (s *Service) createUpstreamActionLog(ctx context.Context, log UpstreamActionLog) (UpstreamActionLog, error) {
	if strings.TrimSpace(log.ResourceType) == "" {
		log.ResourceType = "instance"
	}
	if strings.TrimSpace(log.Source) == "" {
		log.Source = "system"
	}
	if strings.TrimSpace(log.Status) == "" {
		log.Status = "submitted"
	}
	if err := s.db.WithContext(ctx).Create(&log).Error; err != nil {
		return UpstreamActionLog{}, err
	}
	return log, nil
}

func (s *Service) submitUpstreamActionWithAudit(ctx context.Context, instance Instance, actionType string, source string, orderID *int64, refundID *int64, method string, path string, requestPayload any, remark string) (UpstreamActionLog, error) {
	now := time.Now().UTC()
	base := UpstreamActionLog{
		UserID:         &instance.UserID,
		OrderID:        orderID,
		OrderRefundID:  refundID,
		InstanceID:     &instance.ID,
		CloudServerID:  instance.CloudServerID,
		UpstreamID:     instance.UpstreamID,
		ActionType:     actionType,
		ResourceType:   "instance",
		ResourceID:     resourceIDForInstance(instance),
		Source:         source,
		RequestMethod:  stringPtr(method),
		RequestPath:    stringPtr(path),
		RequestPayload: jsonRawFromAny(requestPayload),
		Remark:         stringPtr(remark),
	}
	result, err := s.submitUpstreamPath(ctx, instance, path, method, requestPayload)
	if err != nil {
		message := err.Error()
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, err
	}
	base.ResponsePayload = jsonRawFromAny(result.Body)
	if result.StatusCode < 200 || result.StatusCode >= 300 {
		message := fmt.Sprintf("上游返回异常（HTTP %d）：%s", result.StatusCode, trimText(result.Text, 200))
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, HTTPError{Status: 502, Detail: message}
	}
	if result.TaskID != nil {
		base.Status = "submitted"
		base.UpstreamTaskID = result.TaskID
	} else {
		base.Status = "no_task"
		base.CompletedAt = &now
	}
	return s.createUpstreamActionLog(ctx, base)
}

func (s *Service) submitRenewUpstreamAction(ctx context.Context, instance Instance, orderID *int64, cycles int, amount int, payload map[string]any) (UpstreamActionLog, error) {
	product, _, err := s.productAndGroup(ctx, instance.ProductID)
	if err != nil {
		return UpstreamActionLog{}, err
	}
	if product != nil && product.APIType == "plugin" && product.APIIdentifier != nil && s.pluginActionAllowedOrMissing(*product, "renew") {
		return s.submitPluginManageActionWithAudit(ctx, instance, *product, "renew", payload, "renewal", orderID, nil)
	}
	if instance.UpstreamID == nil {
		return s.recordUnsupportedResourceAction(ctx, instance, "instance_renew", "renewal", orderID, nil, "POST", "/renew", payload, "实例尚未绑定上游资源，无法提交真实续费动作")
	}
	if instance.CloudServerID != nil {
		var server CloudServer
		if err := s.db.WithContext(ctx).Where("id = ?", *instance.CloudServerID).First(&server).Error; err == nil && isZJMFServer(server) {
			return s.submitZJMFRenewWithAudit(ctx, instance, server, orderID, cycles, amount, payload)
		}
	}
	path := fmt.Sprintf("/clouds/%d/renew", *instance.UpstreamID)
	return s.submitUpstreamActionWithAudit(ctx, instance, "instance_renew", "renewal", orderID, nil, "POST", path, payload, "实例续费上游联动")
}

func (s *Service) submitTerminateUpstreamAction(ctx context.Context, instance Instance, orderID *int64, refundID *int64, payload map[string]any) (UpstreamActionLog, error) {
	product, _, err := s.productAndGroup(ctx, instance.ProductID)
	if err != nil {
		return UpstreamActionLog{}, err
	}
	if product != nil && product.APIType == "plugin" && product.APIIdentifier != nil && s.pluginActionAllowedOrMissing(*product, "terminate") {
		return s.submitPluginManageActionWithAudit(ctx, instance, *product, "terminate", payload, "refund", orderID, refundID)
	}
	if instance.UpstreamID == nil {
		return s.recordUnsupportedResourceAction(ctx, instance, "instance_delete", "refund", orderID, refundID, "DELETE", "/resource", payload, "实例尚未绑定上游资源，无法提交真实删除动作")
	}
	if instance.CloudServerID != nil {
		var server CloudServer
		if err := s.db.WithContext(ctx).Where("id = ?", *instance.CloudServerID).First(&server).Error; err == nil && isZJMFServer(server) {
			return s.submitZJMFTerminateWithAudit(ctx, instance, server, orderID, refundID, payload)
		}
	}
	path := fmt.Sprintf("/clouds/%d", *instance.UpstreamID)
	return s.submitUpstreamActionWithAudit(ctx, instance, "instance_delete", "refund", orderID, refundID, "DELETE", path, map[string]any{"force": 1, "delete_data_only": 0}, "退款/取消实例资源删除")
}

func (s *Service) submitZJMFTerminateWithAudit(ctx context.Context, instance Instance, server CloudServer, orderID *int64, refundID *int64, requestPayload map[string]any) (UpstreamActionLog, error) {
	now := time.Now().UTC()
	path := "/host/cancel"
	method := http.MethodPost
	reason := strings.TrimSpace(firstString(requestPayload["reason"], "立即删除"))
	payload := cloneMap(requestPayload)
	payload["id"] = instance.UpstreamID
	payload["type"] = "Immediate"
	payload["reason"] = reason
	base := UpstreamActionLog{
		UserID:         &instance.UserID,
		OrderID:        orderID,
		OrderRefundID:  refundID,
		InstanceID:     &instance.ID,
		CloudServerID:  instance.CloudServerID,
		UpstreamID:     instance.UpstreamID,
		ActionType:     "instance_delete",
		ResourceType:   "instance",
		ResourceID:     resourceIDForInstance(instance),
		Source:         "refund",
		RequestMethod:  &method,
		RequestPath:    &path,
		RequestPayload: jsonRawFromAny(payload),
		Remark:         stringPtr("魔方财务立即停用/删除请求"),
	}
	result, err := submitZJMFCancel(ctx, server, instance, reason)
	if err != nil {
		message := err.Error()
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, err
	}
	base.ResponsePayload = jsonRawFromAny(result.Body)
	if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
		message := fmt.Sprintf("魔方财务取消实例失败：%s", upstreamMessage(result.Body, result.Text))
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, HTTPError{Status: 502, Detail: message}
	}
	base.Status = "submitted"
	if taskID := extractTaskID(result.Payload); taskID != nil {
		base.UpstreamTaskID = taskID
	}
	return s.createUpstreamActionLog(ctx, base)
}

func (s *Service) submitZJMFRenewWithAudit(ctx context.Context, instance Instance, server CloudServer, orderID *int64, cycles int, amount int, requestPayload map[string]any) (UpstreamActionLog, error) {
	now := time.Now().UTC()
	path := "/renew"
	method := "POST"
	base := UpstreamActionLog{
		UserID:         &instance.UserID,
		OrderID:        orderID,
		InstanceID:     &instance.ID,
		CloudServerID:  instance.CloudServerID,
		UpstreamID:     instance.UpstreamID,
		ActionType:     "instance_renew",
		ResourceType:   "instance",
		ResourceID:     resourceIDForInstance(instance),
		Source:         "renewal",
		RequestMethod:  &method,
		RequestPath:    &path,
		RequestPayload: jsonRawFromAny(requestPayload),
		Remark:         stringPtr(fmt.Sprintf("魔方财务实例续费：cycles=%d amount=%d", cycles, amount)),
	}
	result, err := submitZJMFRenew(ctx, server, instance)
	if err != nil {
		message := err.Error()
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, err
	}
	base.ResponsePayload = jsonRawFromAny(result.Body)
	if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
		message := fmt.Sprintf("魔方财务续费失败：%s", upstreamMessage(result.Body, result.Text))
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, HTTPError{Status: 502, Detail: message}
	}
	base.Status = "no_task"
	if taskID := extractTaskID(result.Payload); taskID != nil {
		base.Status = "submitted"
		base.UpstreamTaskID = taskID
	} else {
		base.CompletedAt = &now
	}
	return s.createUpstreamActionLog(ctx, base)
}

func (s *Service) submitPluginManageActionWithAudit(ctx context.Context, instance Instance, product Product, action string, payload map[string]any, source string, orderID *int64, refundID *int64) (UpstreamActionLog, error) {
	now := time.Now().UTC()
	pluginID := strings.TrimSpace(*product.APIIdentifier)
	method := "PLUGIN"
	path := action
	remark := fmt.Sprintf("plugin %s action %s", pluginID, action)
	actionType := canonicalPluginActionType(action, source, orderID)
	base := UpstreamActionLog{
		UserID:         &instance.UserID,
		OrderID:        orderID,
		OrderRefundID:  refundID,
		InstanceID:     &instance.ID,
		CloudServerID:  instance.CloudServerID,
		UpstreamID:     instance.UpstreamID,
		ActionType:     actionType,
		ResourceType:   "instance",
		ResourceID:     resourceIDForInstance(instance),
		Source:         source,
		RequestMethod:  &method,
		RequestPath:    &path,
		RequestPayload: jsonRawFromAny(payload),
		Remark:         &remark,
	}
	response, err := s.callProvisionManage(ctx, s.db, instance, product, pluginID, action, payload)
	if err != nil {
		message := err.Error()
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, err
	}
	base.ResponsePayload = jsonRawFromAny(sanitizePluginManageResult(response.Result))
	if !response.OK {
		message := response.Message
		if strings.TrimSpace(message) == "" {
			message = "插件管理动作执行失败"
		}
		base.Status = "failed"
		base.ErrorMessage = &message
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, BadRequest(message)
	}
	base.Status = "success"
	taskID, outcomeMessage := pluginManageResultOutcome(response.Result)
	base.UpstreamTaskID = taskID
	if outcomeMessage != "" {
		base.Status = "failed"
		base.ErrorMessage = &outcomeMessage
		base.CompletedAt = &now
		log, createErr := s.createUpstreamActionLog(ctx, base)
		if createErr != nil {
			return UpstreamActionLog{}, createErr
		}
		return log, BadRequest(outcomeMessage)
	}
	base.CompletedAt = &now
	log, err := s.createUpstreamActionLog(ctx, base)
	if err != nil {
		return UpstreamActionLog{}, err
	}
	if base.Status == "success" {
		if err := applyPluginManageResult(s.db, &instance, action, response.Result, now); err != nil {
			return log, err
		}
	}
	return log, nil
}

func canonicalPluginActionType(action string, source string, orderID *int64) string {
	switch {
	case source == "renewal" && action == "renew":
		return "instance_renew"
	case source == "refund" && action == "terminate":
		return "instance_delete"
	case source == "lifecycle" && action == "suspend":
		return "instance_suspend"
	case source == "lifecycle" && action == "unsuspend":
		return "instance_unsuspend"
	case orderID != nil && isAdvancedPaidActionType(action):
		return action
	default:
		return "plugin_" + action
	}
}

func pluginManageResultOutcome(result map[string]any) (*int64, string) {
	var taskID *int64
	for _, key := range []string{"taskid", "task_id"} {
		if parsed, ok := int64FromAny(result[key]); ok {
			taskID = &parsed
			break
		}
	}
	if success, ok := result["success"].(bool); ok {
		if !success {
			return taskID, firstString(result["message"], result["msg"], "插件管理动作执行失败")
		}
		return taskID, ""
	}
	if status, ok := stringFromAny(result["status"]); ok {
		switch strings.ToLower(status) {
		case "success", "succeeded", "completed", "finished", "done":
			return taskID, ""
		case "failed", "failure", "error", "cancelled", "canceled":
			return taskID, firstString(result["message"], result["msg"], "插件管理动作执行失败")
		case "submitted", "pending", "running", "processing":
			return taskID, "插件返回了异步任务，但当前 provision 插件协议未提供任务查询能力"
		}
	}
	if taskID != nil {
		return taskID, "插件返回了异步任务 ID，但未声明可验证的完成状态"
	}
	return nil, ""
}

func (s *Service) pluginActionAllowedOrMissing(product Product, action string) bool {
	manifest := s.publicPluginForProduct(product)
	if manifest == nil {
		return false
	}
	return pluginActionAllowed(manifest, action)
}

func (s *Service) SyncPendingUpstreamActions(ctx context.Context, limit int) (map[string]int, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	if limit <= 0 {
		limit = 100
	}
	var actions []UpstreamActionLog
	if err := s.db.WithContext(ctx).
		Where("status IN ?", []string{"submitted", "running"}).
		Order("last_checked_at IS NOT NULL ASC, last_checked_at ASC, id ASC").
		Limit(limit).
		Find(&actions).Error; err != nil {
		return nil, err
	}
	summary := map[string]int{"checked": 0, "success": 0, "failed": 0, "running": 0, "no_task": 0, "errored": 0}
	refundIDs := map[int64]struct{}{}
	for _, action := range actions {
		summary["checked"]++
		status, err := s.syncOneUpstreamAction(ctx, &action)
		if action.OrderRefundID != nil {
			refundIDs[*action.OrderRefundID] = struct{}{}
		}
		if err != nil {
			summary["errored"]++
			continue
		}
		if _, ok := summary[status]; ok {
			summary[status]++
		} else {
			summary["running"]++
		}
	}
	for refundID := range refundIDs {
		if err := s.refreshRefundResourceStatus(ctx, refundID); err != nil {
			return nil, err
		}
	}
	return summary, nil
}

func (s *Service) syncOneUpstreamAction(ctx context.Context, item *UpstreamActionLog) (string, error) {
	now := time.Now().UTC()
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := &Service{db: tx, logger: s.logger, plugins: s.plugins}
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", item.ID).First(item).Error; err != nil {
			return err
		}
		item.AttemptCount++
		item.LastCheckedAt = &now
		if item.UpstreamTaskID == nil {
			item.Status = "no_task"
			item.CompletedAt = &now
			return tx.Save(item).Error
		}
		task, err := txService.checkUpstreamTask(ctx, *item)
		if err != nil {
			item.Status = "running"
			if item.AttemptCount >= 5 {
				item.Status = "failed"
				item.CompletedAt = &now
			}
			message := fmt.Sprintf("任务同步失败：%v", err)
			item.ErrorMessage = &message
			if item.Status == "failed" {
				if err := txService.applyLocalTaskResult(ctx, item, item.Status, map[string]any{"message": message}); err != nil {
					return err
				}
			}
			return tx.Save(item).Error
		}
		item.TaskPayload = jsonRawFromAny(task.Raw)
		if len(item.TaskPayload) == 0 {
			item.TaskPayload = jsonRawFromAny(task.Task)
		}
		item.Status = task.Status
		if task.Status == "success" || task.Status == "failed" || task.Status == "no_task" {
			item.CompletedAt = &now
		}
		if task.Status == "failed" && task.Error != "" {
			item.ErrorMessage = &task.Error
		} else if task.Status != "failed" {
			item.ErrorMessage = nil
		}
		if err := txService.applyLocalTaskResult(ctx, item, task.Status, task.Task); err != nil {
			return err
		}
		return tx.Save(item).Error
	})
	return item.Status, err
}

func (s *Service) applyLocalTaskResult(ctx context.Context, item *UpstreamActionLog, status string, task map[string]any) error {
	if item.OrderID != nil {
		if status == "failed" && isCompensatablePaidActionType(item.ActionType) {
			failure := firstString(task["msg"], task["message"], item.ErrorMessage, "upstream task failed")
			if err := s.compensateAdvancedPaidOrderTx(ctx, *item.OrderID, failure); err != nil {
				return err
			}
		} else if (status == "success" || status == "no_task") && isAdvancedPaidActionType(item.ActionType) {
			if err := s.finalizeAdvancedPaidOrderTx(ctx, *item.OrderID); err != nil {
				return err
			}
		}
	}
	if item.InstanceID == nil {
		return nil
	}
	if status == "failed" {
		msg := firstString(task["msg"], task["message"], item.ErrorMessage, "上游任务失败，待人工处理")
		return s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", *item.InstanceID).Update("provision_log", "上游任务失败，待人工处理："+msg).Error
	}
	if status != "success" {
		return nil
	}
	now := time.Now().UTC()
	switch item.ActionType {
	case "instance_delete", "instance_refund":
		if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", *item.InstanceID).Updates(map[string]any{
			"status":        "terminated",
			"suspend_type":  nil,
			"terminated_at": now,
		}).Error; err != nil {
			return err
		}
		reason := "upstream delete completed"
		if item.Remark != nil && strings.TrimSpace(*item.Remark) != "" {
			reason = strings.TrimSpace(*item.Remark)
		}
		_, err := s.RestockReleasedInstanceTx(ctx, *item.InstanceID, item.Source, reason, "")
		return err
	case "instance_suspend":
		suspendType := "due"
		return s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", *item.InstanceID).Updates(map[string]any{
			"status":       "suspended",
			"suspend_type": suspendType,
		}).Error
	case "instance_unsuspend":
		return s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", *item.InstanceID).Updates(map[string]any{
			"status":       "active",
			"suspend_type": nil,
		}).Error
	default:
		return nil
	}
}

func isCompensatablePaidActionType(actionType string) bool {
	return actionType == "instance_renew" || isAdvancedPaidActionType(actionType)
}

func isAdvancedPaidActionType(actionType string) bool {
	switch actionType {
	case "upgrade", "disk_create", "disk_resize":
		return true
	default:
		return false
	}
}

func (s *Service) refreshRefundResourceStatus(ctx context.Context, refundID int64) error {
	var logs []UpstreamActionLog
	if err := s.db.WithContext(ctx).Where("order_refund_id = ?", refundID).Order("id DESC").Find(&logs).Error; err != nil {
		return err
	}
	status := summarizeResourceStatus(logs)
	var errText *string
	if status == "failed" {
		for _, item := range logs {
			if item.Status == "failed" && item.ErrorMessage != nil {
				errText = item.ErrorMessage
				break
			}
		}
	}
	updates := map[string]any{"resource_status": status, "resource_error": errText}
	if status == "running" {
		updates["resource_completed_at"] = nil
	} else {
		now := time.Now().UTC()
		updates["resource_completed_at"] = &now
	}
	return s.db.WithContext(ctx).Model(&OrderRefund{}).Where("id = ?", refundID).Updates(updates).Error
}

func summarizeResourceStatus(logs []UpstreamActionLog) string {
	if len(logs) == 0 {
		return "none"
	}
	hasRunning := false
	hasManual := false
	hasIgnored := false
	hasFinished := false
	for _, item := range logs {
		if item.Status == "superseded" {
			continue
		}
		switch item.Status {
		case "failed":
			return "failed"
		case "submitted", "running":
			hasRunning = true
		case "manual_handled":
			hasManual = true
		case "ignored":
			hasIgnored = true
		case "success", "no_task":
			hasFinished = true
		}
	}
	if hasRunning {
		return "running"
	}
	if hasManual {
		return "manual_handled"
	}
	if hasIgnored {
		return "ignored"
	}
	if hasFinished {
		return "finished"
	}
	return "none"
}

func resourceIDForInstance(instance Instance) *string {
	if instance.UpstreamID != nil {
		value := strconv.FormatInt(*instance.UpstreamID, 10)
		return &value
	}
	value := strconv.FormatInt(instance.ID, 10)
	return &value
}

func stringPtr(value string) *string {
	if strings.TrimSpace(value) == "" {
		return nil
	}
	return &value
}

func errorsIsRecordNotFound(err error) bool {
	return errors.Is(err, gorm.ErrRecordNotFound)
}

func (s *Service) callProvisionManage(ctx context.Context, tx *gorm.DB, instance Instance, product Product, pluginID string, action string, payload map[string]any) (provisionManageResponse, error) {
	if s.plugins == nil {
		return provisionManageResponse{}, fmt.Errorf("Go provision plugin runtime is not enabled")
	}
	request, err := s.provisionManageRequest(ctx, tx, instance, product, pluginID, action, payload)
	if err != nil {
		return provisionManageResponse{}, err
	}
	req := provisionManageRequestFromContext(pluginID, action, payload, request)
	req.Config = s.plugins.MergeConfig(ctx, pluginID, req.Config)
	response, err := s.plugins.ManageProvision(ctx, pluginID, req)
	if err != nil {
		return provisionManageResponse{}, err
	}
	return provisionManageResponse{OK: response.OK, Message: response.Message, Retryable: response.Retryable, Result: response.Result}, nil
}

func (s *Service) provisionManageRequest(ctx context.Context, tx *gorm.DB, instance Instance, product Product, pluginID string, action string, payload map[string]any) (map[string]any, error) {
	var order Order
	if err := tx.WithContext(ctx).Where("id = ?", instance.OrderID).First(&order).Error; err != nil && !errorsIsRecordNotFound(err) {
		return nil, err
	}
	var user UserAccount
	if err := tx.WithContext(ctx).Where("id = ?", instance.UserID).First(&user).Error; err != nil && !errorsIsRecordNotFound(err) {
		return nil, err
	}
	snapshot := snapshotForManagedInstance(order.ItemsSnapshot, instance)
	sliderValues, _ := snapshot["slider_values"].(map[string]any)
	pluginFields, _ := sliderValues["__plugin_fields"].(map[string]any)
	upstreamParams, _ := snapshot["upstream_params"].(map[string]any)
	interfaceConfig, _ := snapshot["interface_config"].(map[string]any)
	storedInterfaceConfig, err := LoadProvisionInterfaceRuntimeConfig(ctx, tx, product.ProvisionInterfaceID, pluginID)
	if err != nil {
		return nil, err
	}
	for key, value := range interfaceConfig {
		storedInterfaceConfig[key] = value
	}
	interfaceConfig = storedInterfaceConfig
	productConfigValues, _ := snapshot["provision_config"].([]any)
	instanceValues := instanceManageBridgeMap(instance)
	if action == "open-panel" && instance.RootPassword != nil {
		password, err := decryptStoredSecret(*instance.RootPassword)
		if err != nil {
			return nil, err
		}
		instanceValues["root_password"] = password
	}

	return map[string]any{
		"plugin_id":             pluginID,
		"action":                action,
		"payload":               payload,
		"instance":              instanceValues,
		"product":               productManageBridgeMap(product),
		"order":                 orderManageBridgeMap(order),
		"user":                  userManageBridgeMap(user),
		"snapshot":              snapshot,
		"upstream_params":       upstreamParams,
		"interface_config":      interfaceConfig,
		"plugin_fields":         pluginFields,
		"product_config_values": productConfigValues,
	}, nil
}

func provisionManageRequestFromContext(pluginID string, action string, payload map[string]any, request map[string]any) plugins.ProvisionRequest {
	req := plugins.ProvisionRequest{
		PluginID:           pluginID,
		Action:             action,
		ActionPayload:      payload,
		Instance:           mapValueCommerce(request["instance"]),
		Product:            mapValueCommerce(request["product"]),
		Order:              mapValueCommerce(request["order"]),
		User:               mapValueCommerce(request["user"]),
		Snapshot:           mapValueCommerce(request["snapshot"]),
		UpstreamParams:     mapValueCommerce(request["upstream_params"]),
		InterfaceConfig:    mapValueCommerce(request["interface_config"]),
		PluginFields:       mapValueCommerce(request["plugin_fields"]),
		BackwardCompatible: request,
	}
	if values, ok := request["product_config_values"].([]any); ok {
		req.ProductConfigValues = values
	}
	return req
}

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

func snapshotForManagedInstance(raw JSONRaw, instance Instance) map[string]any {
	items := []any{}
	if len(raw) > 0 {
		_ = json.Unmarshal(raw, &items)
	}
	var fallback map[string]any
	for _, item := range items {
		snapshot, ok := item.(map[string]any)
		if !ok {
			continue
		}
		if fallback == nil {
			fallback = snapshot
		}
		if id, ok := int64FromAny(snapshot["product_id"]); ok && id == instance.ProductID {
			return snapshot
		}
	}
	if fallback != nil {
		return fallback
	}
	return map[string]any{}
}

func instanceManageBridgeMap(instance Instance) map[string]any {
	return map[string]any{
		"id":                  instance.ID,
		"user_id":             instance.UserID,
		"order_id":            instance.OrderID,
		"product_id":          instance.ProductID,
		"cloud_server_id":     instance.CloudServerID,
		"status":              instance.Status,
		"suspend_type":        instance.SuspendType,
		"cycle":               instance.Cycle,
		"unit_price":          instance.UnitPrice,
		"auto_renew":          instance.AutoRenew,
		"next_renew_amount":   instance.NextRenewAmount,
		"alias":               instance.Alias,
		"hostname":            instance.Hostname,
		"os_image":            instance.OSImage,
		"cpu":                 instance.CPU,
		"memory":              instance.Memory,
		"expires_at":          timePtrString(instance.ExpiresAt),
		"upstream_id":         instance.UpstreamID,
		"upstream_task_id":    instance.UpstreamTaskID,
		"upstream_invoice_id": instance.UpstreamInvoiceID,
		"upstream_payload":    jsonAnyValue(instance.UpstreamPayload),
		"main_ip":             instance.MainIP,
		"ssh_port":            instance.SSHPort,
		"provision_attempts":  instance.ProvisionAttempts,
		"last_provision_at":   timePtrString(instance.LastProvisionAt),
		"provision_log":       instance.ProvisionLog,
		"terminated_at":       timePtrString(instance.TerminatedAt),
		"created_at":          instance.CreatedAt.Format(time.RFC3339),
		"updated_at":          instance.UpdatedAt.Format(time.RFC3339),
	}
}

func productManageBridgeMap(product Product) map[string]any {
	return map[string]any{
		"id":                     product.ID,
		"group_id":               product.GroupID,
		"sku":                    product.SKU,
		"name":                   product.Name,
		"type":                   product.Type,
		"status":                 product.Status,
		"api_type":               product.APIType,
		"api_identifier":         product.APIIdentifier,
		"provision_interface_id": product.ProvisionInterfaceID,
		"provision_config":       jsonAnyValue(product.ProvisionConfig),
	}
}

func orderManageBridgeMap(order Order) map[string]any {
	if order.ID == 0 {
		return nil
	}
	return map[string]any{
		"id":              order.ID,
		"user_id":         order.UserID,
		"out_order_no":    order.OutOrderNo,
		"status":          order.Status,
		"total_amount":    order.TotalAmount,
		"discount_amount": order.DiscountAmount,
		"promo_code_id":   order.PromoCodeID,
		"aff_code":        order.AffCode,
	}
}

func userManageBridgeMap(user UserAccount) map[string]any {
	if user.ID == 0 {
		return nil
	}
	return map[string]any{
		"id":             user.ID,
		"balance":        user.Balance,
		"invite_code":    user.InviteCode,
		"bound_agent_id": user.BoundAgentID,
	}
}

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

func pluginActionAllowed(manifest map[string]any, action string) bool {
	for _, item := range asSlice(manifest["manage_actions"]) {
		entry, ok := item.(map[string]any)
		if !ok {
			continue
		}
		if strings.TrimSpace(fmt.Sprint(entry["key"])) == action {
			return true
		}
	}
	return false
}

func pluginActionAudience(manifest map[string]any, action string) string {
	for _, item := range asSlice(manifest["manage_actions"]) {
		entry, ok := item.(map[string]any)
		if !ok {
			continue
		}
		if strings.TrimSpace(fmt.Sprint(entry["key"])) == action {
			audience := strings.TrimSpace(fmt.Sprint(entry["audience"]))
			if audience == "" || audience == "<nil>" {
				return "both"
			}
			return audience
		}
	}
	return "both"
}

func sanitizePluginManageResult(result map[string]any) map[string]any {
	if result == nil {
		return map[string]any{}
	}
	out := sanitizeSecretPayload(result)
	if payload, ok := out["payload"].(map[string]any); ok {
		out["payload"] = sanitizeSecretPayload(payload)
	}
	return out
}

func sanitizeSecretPayload(payload map[string]any) map[string]any {
	out := cloneMap(payload)
	for _, key := range []string{"root_password", "new_password", "password"} {
		if _, ok := out[key]; ok {
			out[key] = "***"
		}
	}
	return out
}

func applyPluginManageResult(tx *gorm.DB, instance *Instance, action string, result map[string]any, now time.Time) error {
	if result == nil {
		result = map[string]any{}
	}
	payload, _ := result["payload"].(map[string]any)
	if rawPassword, ok := stringFromAny(firstNonNil(result["root_password"], result["password"], payload["root_password"], payload["password"])); ok {
		encrypted, err := EncryptStoredSecret(rawPassword)
		if err != nil {
			return err
		}
		instance.RootPassword = &encrypted
	}
	if statusText, ok := stringFromAny(firstNonNil(result["instance_status"], payload["instance_status"])); ok {
		switch statusText {
		case "active":
			instance.Status = "active"
			instance.SuspendType = nil
		case "suspended":
			instance.Status = "suspended"
			if instance.SuspendType == nil {
				suspendType := "manual"
				instance.SuspendType = &suspendType
			}
		case "terminated":
			instance.Status = "terminated"
			if instance.TerminatedAt == nil {
				instance.TerminatedAt = &now
			}
		case "provision_failed":
			instance.Status = "provision_failed"
		}
	}
	upstreamPayload := jsonMap(instance.UpstreamPayload)
	if upstreamPayload == nil {
		upstreamPayload = map[string]any{}
	}
	logs := []any{}
	if raw, ok := upstreamPayload["plugin_actions"].([]any); ok {
		logs = raw
	}
	if len(logs) > 19 {
		logs = logs[len(logs)-19:]
	}
	logs = append(logs, map[string]any{
		"action":     action,
		"message":    result["message"],
		"task_id":    result["task_id"],
		"payload":    sanitizeSecretPayload(payload),
		"created_at": now.Format(time.RFC3339),
	})
	upstreamPayload["plugin_actions"] = logs
	instance.UpstreamPayload = jsonRawFromAny(upstreamPayload)
	return tx.Save(instance).Error
}

func firstNonNil(values ...any) any {
	for _, value := range values {
		if value != nil {
			return value
		}
	}
	return nil
}

func stringFromAny(value any) (string, bool) {
	switch v := value.(type) {
	case string:
		text := strings.TrimSpace(v)
		return text, text != ""
	case fmt.Stringer:
		text := strings.TrimSpace(v.String())
		return text, text != ""
	default:
		return "", false
	}
}

func int64FromAny(value any) (int64, bool) {
	switch v := value.(type) {
	case int:
		return int64(v), true
	case int8:
		return int64(v), true
	case int16:
		return int64(v), true
	case int32:
		return int64(v), true
	case int64:
		return v, true
	case uint:
		return int64(v), true
	case uint8:
		return int64(v), true
	case uint16:
		return int64(v), true
	case uint32:
		return int64(v), true
	case uint64:
		if v > uint64(^uint64(0)>>1) {
			return 0, false
		}
		return int64(v), true
	case float64:
		return int64(v), true
	case json.Number:
		parsed, err := v.Int64()
		return parsed, err == nil
	case string:
		if strings.TrimSpace(v) == "" {
			return 0, false
		}
		parsed, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
		return parsed, err == nil
	default:
		return 0, false
	}
}
