package commerce

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"gorm.io/gorm"
)

type upstreamCallResult struct {
	StatusCode int
	Text       string
	Body       map[string]any
	Payload    map[string]any
	TaskID     *int64
}

type upstreamTaskResult struct {
	Status string
	Task   map[string]any
	Raw    any
	Error  string
}

func (s *Service) submitUpstreamPath(ctx context.Context, instance Instance, path string, method string, payload any) (upstreamCallResult, error) {
	if instance.CloudServerID == nil {
		return upstreamCallResult{}, BadRequest("实例未绑定云节点")
	}
	var server CloudServer
	if err := s.db.WithContext(ctx).Where("id = ?", *instance.CloudServerID).First(&server).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return upstreamCallResult{}, BadRequest("上游节点不存在")
		}
		return upstreamCallResult{}, err
	}
	if server.Disabled {
		return upstreamCallResult{}, BadRequest("上游节点已禁用")
	}
	if isZJMFServer(server) {
		return callZJMFPath(ctx, server, instance, path, method, payload)
	}
	return callDCIMPath(ctx, server, path, method, payload, 30*time.Second)
}

func (s *Service) checkUpstreamTask(ctx context.Context, item UpstreamActionLog) (upstreamTaskResult, error) {
	if item.RequestMethod != nil && strings.EqualFold(strings.TrimSpace(*item.RequestMethod), "PLUGIN") {
		return upstreamTaskResult{}, fmt.Errorf("provision plugin async task query is not supported")
	}
	if item.CloudServerID == nil {
		return upstreamTaskResult{Status: "failed", Error: "缺少云节点"}, nil
	}
	var server CloudServer
	if err := s.db.WithContext(ctx).Where("id = ?", *item.CloudServerID).First(&server).Error; err != nil {
		return upstreamTaskResult{}, err
	}
	if isZJMFServer(server) {
		if item.InstanceID == nil {
			return upstreamTaskResult{}, fmt.Errorf("魔方财务任务缺少实例")
		}
		var instance Instance
		if err := s.db.WithContext(ctx).Where("id = ?", *item.InstanceID).First(&instance).Error; err != nil {
			return upstreamTaskResult{}, err
		}
		if instance.UpstreamID == nil {
			return upstreamTaskResult{}, fmt.Errorf("魔方财务实例缺少上游 ID")
		}
		if item.UpstreamTaskID == nil && (item.ActionType == "instance_delete" || item.ActionType == "instance_refund") {
			return checkZJMFTermination(ctx, server, instance)
		}
		if item.UpstreamTaskID == nil {
			return upstreamTaskResult{}, fmt.Errorf("魔方财务任务缺少任务 ID")
		}
		result, err := callZJMFPath(ctx, server, instance, fmt.Sprintf("/clouds/%d/tasks", *instance.UpstreamID), "GET", nil)
		if err != nil {
			return upstreamTaskResult{}, err
		}
		for _, raw := range asSlice(result.Payload["list"]) {
			task, ok := raw.(map[string]any)
			if !ok {
				continue
			}
			id, ok := int64FromAny(task["id"])
			if !ok || id != *item.UpstreamTaskID {
				continue
			}
			status := resolveUpstreamTaskStatus(task)
			errText := ""
			if status == "failed" {
				errText = firstString(task["msg"], task["message"], "魔方财务任务失败")
			}
			return upstreamTaskResult{Status: status, Task: task, Raw: result.Body, Error: errText}, nil
		}
		return upstreamTaskResult{}, fmt.Errorf("魔方财务未返回任务 %d 的可验证状态", *item.UpstreamTaskID)
	}
	if item.UpstreamTaskID == nil {
		return upstreamTaskResult{Status: "no_task"}, nil
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/tasks/%d", *item.UpstreamTaskID), "GET", nil, 8*time.Second)
	if err != nil {
		return upstreamTaskResult{}, err
	}
	if result.StatusCode == http.StatusNotFound {
		return upstreamTaskResult{}, fmt.Errorf("上游任务 %d 不存在，无法确认是否成功", *item.UpstreamTaskID)
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 {
		return upstreamTaskResult{}, fmt.Errorf("HTTP %d: %s", result.StatusCode, trimText(result.Text, 200))
	}
	status := resolveUpstreamTaskStatus(result.Payload)
	errText := ""
	if status == "failed" {
		errText = firstString(result.Payload["msg"], result.Payload["message"], "上游任务失败")
	}
	return upstreamTaskResult{Status: status, Task: result.Payload, Raw: result.Body, Error: errText}, nil
}

func callDCIMPath(ctx context.Context, server CloudServer, path string, method string, payload any, timeout time.Duration) (upstreamCallResult, error) {
	password, err := decryptStoredSecret(server.PasswordEncrypted)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("上游节点密码解密失败：%w", err)
	}
	client := &http.Client{Timeout: timeout}
	token, err := dcimLogin(ctx, client, server, password)
	if err != nil {
		return upstreamCallResult{}, err
	}
	return dcimRequest(ctx, client, server, token, path, method, payload)
}

func CallDCIMPath(ctx context.Context, server CloudServer, path string, method string, payload any, timeout time.Duration) (upstreamCallResult, error) {
	return callDCIMPath(ctx, server, path, method, payload, timeout)
}

func dcimLogin(ctx context.Context, client *http.Client, server CloudServer, password string) (string, error) {
	values := url.Values{}
	values.Set("username", server.Username)
	values.Set("password", password)
	loginPath := "/login?a=a"
	if server.AccountType == "agent" {
		loginPath = "/token"
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, dcimURL(server, loginPath), strings.NewReader(values.Encode()))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("User-Agent", "QingYun-Cloud/1.0")
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("上游登录失败：%w", err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	text := string(raw)
	body := decodeJSONObject(raw)
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return "", fmt.Errorf("上游登录失败（HTTP %d）：%s", resp.StatusCode, trimText(text, 200))
	}
	if server.AccountType == "agent" {
		data, _ := body["data"].(map[string]any)
		token := strings.TrimSpace(fmt.Sprint(data["token"]))
		if token == "" || token == "<nil>" {
			return "", fmt.Errorf("上游登录失败：响应未包含 token")
		}
		return token, nil
	}
	token := strings.Trim(strings.TrimSpace(text), `"`)
	if token == "" {
		return "", fmt.Errorf("上游登录失败：响应体为空")
	}
	return token, nil
}

func dcimRequest(ctx context.Context, client *http.Client, server CloudServer, token string, path string, method string, payload any) (upstreamCallResult, error) {
	var body io.Reader
	requestURL := dcimURL(server, path)
	method = strings.ToUpper(method)
	if values := formValues(payload); len(values) > 0 {
		encoded := values.Encode()
		if method == http.MethodGet {
			separator := "?"
			if strings.Contains(requestURL, "?") {
				separator = "&"
			}
			requestURL += separator + encoded
		} else {
			body = strings.NewReader(encoded)
		}
	}
	req, err := http.NewRequestWithContext(ctx, method, requestURL, body)
	if err != nil {
		return upstreamCallResult{}, err
	}
	req.Header.Set("User-Agent", "QingYun-Cloud/1.0")
	req.Header.Set("access-token", token)
	if body != nil {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}
	resp, err := client.Do(req)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("上游调用失败：%w", err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	text := string(raw)
	bodyMap := decodeJSONObject(raw)
	payloadMap := extractPayload(bodyMap)
	return upstreamCallResult{
		StatusCode: resp.StatusCode,
		Text:       text,
		Body:       bodyMap,
		Payload:    payloadMap,
		TaskID:     extractTaskID(payloadMap),
	}, nil
}

func formValues(payload any) url.Values {
	values := url.Values{}
	switch data := payload.(type) {
	case nil:
	case url.Values:
		for key, list := range data {
			for _, value := range list {
				values.Add(key, value)
			}
		}
	case map[string]any:
		for key, value := range data {
			switch typed := value.(type) {
			case nil:
			case []string:
				for _, item := range typed {
					values.Add(key, item)
				}
			case []int64:
				for _, item := range typed {
					values.Add(key, strconv.FormatInt(item, 10))
				}
			case []int:
				for _, item := range typed {
					values.Add(key, strconv.Itoa(item))
				}
			case []any:
				for _, item := range typed {
					values.Add(key, fmt.Sprint(item))
				}
			default:
				values.Set(key, fmt.Sprint(value))
			}
		}
	}
	return values
}

func queryPayload(path string) map[string]any {
	out := map[string]any{}
	parts := strings.SplitN(path, "?", 2)
	if len(parts) != 2 {
		return out
	}
	values, err := url.ParseQuery(parts[1])
	if err != nil {
		return out
	}
	for key, list := range values {
		if len(list) == 1 {
			out[key] = list[0]
		} else if len(list) > 1 {
			items := make([]any, 0, len(list))
			for _, item := range list {
				items = append(items, item)
			}
			out[key] = items
		}
	}
	return out
}

func mergeStringAny(base map[string]any, extra map[string]any) map[string]any {
	out := map[string]any{}
	for key, value := range base {
		out[key] = value
	}
	for key, value := range extra {
		out[key] = value
	}
	return out
}

func pathSegment(path string, index int) string {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if index < 0 || index >= len(parts) {
		return ""
	}
	return parts[index]
}

func callZJMFPath(ctx context.Context, server CloudServer, instance Instance, path string, method string, payload any) (upstreamCallResult, error) {
	suffix := zjmfCloudSuffix(instance, path)
	if suffix == "" && strings.ToUpper(method) == "DELETE" {
		return upstreamCallResult{}, BadRequest("魔方财务实例不支持通过本站直接销毁或改配，请使用魔方财务取消/升降级流程")
	}
	action, ok := map[string]string{
		"/on":             "on",
		"/off":            "off",
		"/reboot":         "reboot",
		"/hardoff":        "hard_off",
		"/hard_off":       "hard_off",
		"/password":       "password",
		"/repassword":     "password",
		"/reset-password": "password",
		"/reinstall":      "reinstall",
		"/vnc":            "vnc",
	}[suffix]
	password, err := decryptStoredSecret(server.PasswordEncrypted)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("上游节点密码解密失败：%w", err)
	}
	client := &http.Client{Timeout: 30 * time.Second}
	jwt, err := zjmfLogin(ctx, client, server, password)
	if err != nil {
		return upstreamCallResult{}, err
	}
	hostID := strconv.FormatInt(*instance.UpstreamID, 10)
	payloadMap, _ := payload.(map[string]any)
	if !ok {
		return callZJMFAdvancedPath(ctx, client, server, jwt, hostID, suffix, path, method, payloadMap)
	}
	result, defaultErr := zjmfProvisionDefault(ctx, client, server, jwt, hostID, action, payloadMap)
	if defaultErr == nil {
		return result, nil
	}
	moduleResult, moduleErr := zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s/module/%s", hostID, action), "PUT", payloadMap)
	if moduleErr != nil {
		return upstreamCallResult{}, defaultErr
	}
	if moduleResult.StatusCode < 200 || moduleResult.StatusCode >= 300 || !zjmfSuccess(moduleResult.Body) {
		return upstreamCallResult{}, defaultErr
	}
	return moduleResult, nil
}

type zjmfEndpointAttempt struct {
	Kind   string
	Name   string
	Method string
	Data   map[string]any
}

func callZJMFAdvancedPath(ctx context.Context, client *http.Client, server CloudServer, jwt string, hostID string, suffix string, rawPath string, method string, payload map[string]any) (upstreamCallResult, error) {
	data := mergeStringAny(queryPayload(rawPath), payload)
	cleanSuffix := strings.TrimRight(strings.SplitN(suffix, "?", 2)[0], "/")
	upperMethod := strings.ToUpper(method)
	switch {
	case cleanSuffix == "" && upperMethod == http.MethodGet:
		result, err := zjmfRequest(ctx, client, server, jwt, "/host/header", http.MethodGet, map[string]any{"host_id": hostID})
		if err == nil && result.StatusCode >= 200 && result.StatusCode < 300 && zjmfSuccess(result.Body) {
			return result, nil
		}
		fallback, fallbackErr := zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s", hostID), http.MethodGet, nil)
		if fallbackErr != nil {
			if err != nil {
				return upstreamCallResult{}, err
			}
			return upstreamCallResult{}, fallbackErr
		}
		return fallback, nil
	case cleanSuffix == "/nat_acl" && upperMethod == http.MethodGet:
		natData := mergeStringAny(data, map[string]any{"nat": 1, "host_id": hostID})
		result, err := zjmfRequest(ctx, client, server, jwt, "/host/header", "GET", natData)
		if err == nil && result.StatusCode >= 200 && result.StatusCode < 300 && zjmfSuccess(result.Body) {
			return result, nil
		}
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "custom", Name: "nat_acl", Method: "GET", Data: data},
			{Kind: "func", Name: "listNatAcl", Method: "POST", Data: data},
			{Kind: "module", Name: "nat_acl", Method: "GET", Data: data},
		}, "nat_acl")
	case cleanSuffix == "/nat_acl" && upperMethod == http.MethodPost:
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "func", Name: "addNatAcl", Method: "POST", Data: data},
		}, "addNatAcl")
	case strings.HasPrefix(cleanSuffix, "/nat_acl/") && upperMethod == http.MethodDelete:
		id := strings.TrimPrefix(cleanSuffix, "/nat_acl/")
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "func", Name: "delNatAcl", Method: "POST", Data: map[string]any{"id": id}},
		}, "delNatAcl")
	case cleanSuffix == "/snapshots" && upperMethod == http.MethodGet:
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "func", Name: "listSnapBackup", Method: "POST", Data: data},
			{Kind: "module", Name: "snapshot", Method: "GET", Data: data},
			{Kind: "custom", Name: "snapshot", Method: "GET", Data: data},
			{Kind: "module", Name: "snapshots", Method: "GET", Data: data},
		}, "snapshot")
	case strings.HasPrefix(cleanSuffix, "/disks/") && strings.HasSuffix(cleanSuffix, "/snapshots") && upperMethod == http.MethodPost:
		diskID := pathSegment(cleanSuffix, 1)
		snapshotType := strings.TrimSpace(fmt.Sprint(data["type"]))
		fn := "createSnap"
		if snapshotType == "backup" {
			fn = "createBackup"
		}
		displayName := strings.TrimSpace(firstString(data["remarks"], data["name"]))
		if displayName == "" {
			displayName = snapshotType + "-" + time.Now().UTC().Format("20060102150405")
		}
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "func", Name: fn, Method: "POST", Data: map[string]any{"id": diskID, "name": displayName}},
		}, fn)
	case strings.HasPrefix(cleanSuffix, "/snapshots/") && strings.HasSuffix(cleanSuffix, "/restore") && upperMethod == http.MethodPost:
		snapshotID := pathSegment(cleanSuffix, 1)
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "func", Name: "restoreSnap", Method: "POST", Data: map[string]any{"id": snapshotID}},
			{Kind: "func", Name: "restoreBackup", Method: "POST", Data: map[string]any{"id": snapshotID}},
		}, "restore snapshot")
	case strings.HasPrefix(cleanSuffix, "/snapshots/") && upperMethod == http.MethodDelete:
		snapshotID := pathSegment(cleanSuffix, 1)
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "func", Name: "deleteSnap", Method: "POST", Data: map[string]any{"id": snapshotID}},
			{Kind: "func", Name: "deleteBackup", Method: "POST", Data: map[string]any{"id": snapshotID}},
		}, "delete snapshot")
	case cleanSuffix == "/cron_snap" && upperMethod == http.MethodGet:
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "module", Name: "cron_snap", Method: "GET", Data: data},
			{Kind: "module", Name: "cron_snapshot", Method: "GET", Data: data},
			{Kind: "custom", Name: "cron_snap", Method: "GET", Data: data},
			{Kind: "custom", Name: "cron_snapshot", Method: "GET", Data: data},
		}, "cron snapshot")
	case cleanSuffix == "/cron_snap" && upperMethod == http.MethodPost:
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "module", Name: "cron_snap", Method: "POST", Data: data},
			{Kind: "module", Name: "cron_snapshot", Method: "POST", Data: data},
		}, "create cron snapshot")
	case strings.HasPrefix(cleanSuffix, "/cron_snap/") && upperMethod == http.MethodDelete:
		cronID := pathSegment(cleanSuffix, 1)
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "module", Name: "cron_snap/" + cronID, Method: "DELETE", Data: data},
			{Kind: "module", Name: "cron_snapshot/" + cronID, Method: "DELETE", Data: data},
		}, "delete cron snapshot")
	case cleanSuffix == "/firewall" && upperMethod == http.MethodGet:
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "module", Name: "security_groups", Method: "GET", Data: data},
			{Kind: "custom", Name: "security_groups", Method: "GET", Data: data},
			{Kind: "module", Name: "firewall", Method: "GET", Data: data},
			{Kind: "custom", Name: "firewall", Method: "GET", Data: data},
		}, "firewall")
	case cleanSuffix == "/charts" && upperMethod == http.MethodGet:
		if strings.TrimSpace(fmt.Sprint(data["type"])) == "" {
			data["type"] = "cpu"
		}
		return zjmfFirstEndpoint(ctx, client, server, jwt, hostID, []zjmfEndpointAttempt{
			{Kind: "module", Name: "charts", Method: "GET", Data: data},
		}, "charts")
	case cleanSuffix == "/tasks" && upperMethod == http.MethodGet:
		return zjmfTaskList(ctx, client, server, jwt, hostID)
	}
	return upstreamCallResult{}, BadRequest(fmt.Sprintf("魔方财务实例暂不支持该上游接口：%s", rawPath))
}

func zjmfFirstEndpoint(ctx context.Context, client *http.Client, server CloudServer, jwt string, hostID string, attempts []zjmfEndpointAttempt, label string) (upstreamCallResult, error) {
	var last error
	for _, attempt := range attempts {
		result, err := zjmfEndpoint(ctx, client, server, jwt, hostID, attempt)
		if err != nil {
			last = err
			continue
		}
		if result.StatusCode < 200 || result.StatusCode >= 300 {
			last = fmt.Errorf("zjmf %s returned HTTP %d: %s", label, result.StatusCode, trimText(result.Text, 200))
			continue
		}
		if len(result.Body) == 0 && strings.TrimSpace(result.Text) != "" {
			result.Body = map[string]any{"html": result.Text}
			result.Payload = result.Body
			return result, nil
		}
		if zjmfSuccess(result.Body) || len(result.Payload) > 0 || len(result.Body) > 0 {
			return result, nil
		}
		last = fmt.Errorf("zjmf %s returned empty payload", label)
	}
	if last == nil {
		last = fmt.Errorf("zjmf %s unsupported", label)
	}
	return upstreamCallResult{}, last
}

func zjmfEndpoint(ctx context.Context, client *http.Client, server CloudServer, jwt string, hostID string, attempt zjmfEndpointAttempt) (upstreamCallResult, error) {
	method := attempt.Method
	if method == "" {
		method = "GET"
	}
	data := map[string]any{}
	for key, value := range attempt.Data {
		data[key] = value
	}
	switch attempt.Kind {
	case "func":
		data["func"] = attempt.Name
		return zjmfRequest(ctx, client, server, jwt, "/provision/custom/"+hostID, "POST", data)
	case "custom":
		data["id"] = hostID
		data["key"] = attempt.Name
		return zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s/module/custom", hostID), method, data)
	default:
		return zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s/module/%s", hostID, attempt.Name), method, data)
	}
}

func zjmfTaskList(ctx context.Context, client *http.Client, server CloudServer, jwt string, hostID string) (upstreamCallResult, error) {
	now := time.Now().UTC().Format(time.RFC3339)
	tasks := []any{}
	statusResult, statusErr := zjmfProvisionDefault(ctx, client, server, jwt, hostID, "status", map[string]any{})
	if statusErr != nil || statusResult.StatusCode == http.StatusNotFound || statusResult.StatusCode == http.StatusMethodNotAllowed || !zjmfSuccess(statusResult.Body) {
		statusResult, _ = zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s/module/status", hostID), "GET", map[string]any{"type": "host"})
	}
	if zjmfSuccess(statusResult.Body) {
		payload := statusResult.Payload
		tasks = append(tasks, map[string]any{
			"id":          1,
			"task_type":   "status",
			"status":      2,
			"progress":    100,
			"msg":         firstString(payload["msg"], payload["message"], payload["des"], payload["status"], "zjmf status synced"),
			"hostid":      hostID,
			"rel_type":    "zjmf_finance",
			"provider":    "zjmf_finance",
			"create_time": now,
			"start_time":  now,
			"end_time":    now,
		})
	}
	reinstallResult, _ := zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s/module/status", hostID), "GET", map[string]any{"type": "reinstall"})
	if zjmfSuccess(reinstallResult.Body) {
		payload := reinstallResult.Payload
		progress := int64(100)
		if parsed, ok := int64FromAny(payload["progress"]); ok {
			progress = parsed
		}
		status := 1
		endTime := ""
		if progress >= 100 {
			status = 2
			endTime = now
		}
		tasks = append(tasks, map[string]any{
			"id":          2,
			"task_type":   "reinstall",
			"status":      status,
			"progress":    progress,
			"msg":         firstString(payload["msg"], payload["message"], payload["des"], "zjmf reinstall status synced"),
			"hostid":      hostID,
			"rel_type":    "zjmf_finance",
			"provider":    "zjmf_finance",
			"create_time": now,
			"start_time":  now,
			"end_time":    endTime,
		})
	}
	body := map[string]any{
		"status": 200,
		"data": map[string]any{
			"list":     tasks,
			"provider": "zjmf_finance",
			"meta": map[string]any{
				"total":    len(tasks),
				"page":     1,
				"per_page": len(tasks),
			},
		},
	}
	return upstreamCallResult{StatusCode: http.StatusOK, Body: body, Payload: extractPayload(body), Text: ""}, nil
}

func submitZJMFRenew(ctx context.Context, server CloudServer, instance Instance) (upstreamCallResult, error) {
	if instance.UpstreamID == nil {
		return upstreamCallResult{}, BadRequest("实例尚未开通，无法续费")
	}
	password, err := decryptStoredSecret(server.PasswordEncrypted)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("上游节点密码解密失败：%w", err)
	}
	client := &http.Client{Timeout: 45 * time.Second}
	jwt, err := zjmfLogin(ctx, client, server, password)
	if err != nil {
		return upstreamCallResult{}, err
	}
	hostID := strconv.FormatInt(*instance.UpstreamID, 10)
	return zjmfRequest(ctx, client, server, jwt, fmt.Sprintf("/v1/hosts/%s/renew", hostID), "POST", map[string]any{"billingcycle": instance.Cycle})
}

func submitZJMFCancel(ctx context.Context, server CloudServer, instance Instance, reason string) (upstreamCallResult, error) {
	if instance.UpstreamID == nil {
		return upstreamCallResult{}, BadRequest("实例尚未开通，无法取消")
	}
	password, err := decryptStoredSecret(server.PasswordEncrypted)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("上游节点密码解密失败：%w", err)
	}
	client := &http.Client{Timeout: 30 * time.Second}
	jwt, err := zjmfLogin(ctx, client, server, password)
	if err != nil {
		return upstreamCallResult{}, err
	}
	if strings.TrimSpace(reason) == "" {
		reason = "立即删除"
	}
	return zjmfRequest(ctx, client, server, jwt, "/host/cancel", http.MethodPost, map[string]any{
		"id":     strconv.FormatInt(*instance.UpstreamID, 10),
		"type":   "Immediate",
		"reason": reason,
	})
}

func checkZJMFTermination(ctx context.Context, server CloudServer, instance Instance) (upstreamTaskResult, error) {
	result, err := callZJMFPath(ctx, server, instance, fmt.Sprintf("/clouds/%d", *instance.UpstreamID), http.MethodGet, nil)
	if err != nil {
		return upstreamTaskResult{}, err
	}
	if result.StatusCode == http.StatusNotFound {
		return upstreamTaskResult{Status: "success", Task: map[string]any{"status": "deleted", "message": "魔方财务主机已不存在"}, Raw: result.Body}, nil
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
		return upstreamTaskResult{}, fmt.Errorf("魔方财务取消状态查询失败：%s", upstreamMessage(result.Body, result.Text))
	}
	detail := result.Payload
	if nested, ok := detail["host_data"].(map[string]any); ok {
		detail = nested
	}
	status := strings.ToLower(strings.TrimSpace(firstString(detail["domainstatus"], detail["status"], result.Payload["domainstatus"], result.Payload["status"])))
	switch status {
	case "deleted", "terminated", "cancelled", "canceled":
		return upstreamTaskResult{Status: "success", Task: detail, Raw: result.Body}, nil
	case "active", "suspended", "pending", "processing", "cancelling", "canceling", "deleting", "":
		return upstreamTaskResult{Status: "running", Task: detail, Raw: result.Body}, nil
	default:
		return upstreamTaskResult{Status: "running", Task: detail, Raw: result.Body}, nil
	}
}

func CallZJMFAPI(ctx context.Context, server CloudServer, path string, method string, payload map[string]any, timeout time.Duration) (upstreamCallResult, error) {
	password, err := decryptStoredSecret(server.PasswordEncrypted)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("upstream password decrypt failed: %w", err)
	}
	client := &http.Client{Timeout: timeout}
	jwt, err := zjmfLogin(ctx, client, server, password)
	if err != nil {
		return upstreamCallResult{}, err
	}
	return zjmfRequest(ctx, client, server, jwt, path, method, payload)
}

func zjmfLogin(ctx context.Context, client *http.Client, server CloudServer, password string) (string, error) {
	payload := map[string]any{"username": server.Username, "password": password}
	if server.AccountType == "resource" {
		payload["type"] = "agent"
	}
	paths := []string{"/zjmf_api_login"}
	if server.AccountType == "resource" {
		paths = []string{"/resource_login", "/resource_login_supplier"}
	}
	var last error
	for _, path := range paths {
		result, err := zjmfRequest(ctx, client, server, "", path, "POST", payload)
		if err != nil {
			last = err
			continue
		}
		if result.StatusCode == http.StatusNotFound && path != paths[len(paths)-1] {
			last = fmt.Errorf("login endpoint %s returned 404", path)
			continue
		}
		if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
			return "", fmt.Errorf("魔方财务登录失败：%s", upstreamMessage(result.Body, result.Text))
		}
		jwt := strings.TrimSpace(fmt.Sprint(result.Body["jwt"]))
		if jwt == "" || jwt == "<nil>" {
			return "", fmt.Errorf("魔方财务登录失败：响应未包含 jwt")
		}
		return jwt, nil
	}
	return "", last
}

func zjmfProvisionDefault(ctx context.Context, client *http.Client, server CloudServer, jwt string, hostID string, action string, payload map[string]any) (upstreamCallResult, error) {
	data := map[string]any{"id": hostID, "func": action, "is_api": 1}
	for key, value := range payload {
		data[key] = value
	}
	result, err := zjmfRequest(ctx, client, server, jwt, "/provision/default", "POST", data)
	if err != nil {
		return upstreamCallResult{}, err
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 || !zjmfSuccess(result.Body) {
		return upstreamCallResult{}, fmt.Errorf("zjmf %s failed: %s", action, upstreamMessage(result.Body, result.Text))
	}
	return result, nil
}

func zjmfRequest(ctx context.Context, client *http.Client, server CloudServer, jwt string, path string, method string, payload map[string]any) (upstreamCallResult, error) {
	var body io.Reader
	requestURL := baseURL(server) + path
	method = strings.ToUpper(method)
	if values := nestedFormValues(payload); len(values) > 0 {
		encoded := values.Encode()
		if method == http.MethodGet {
			separator := "?"
			if strings.Contains(requestURL, "?") {
				separator = "&"
			}
			requestURL += separator + encoded
		} else {
			body = strings.NewReader(encoded)
		}
	}
	req, err := http.NewRequestWithContext(ctx, method, requestURL, body)
	if err != nil {
		return upstreamCallResult{}, err
	}
	req.Header.Set("User-Agent", "QYCW-ZJMF-Finance/1.0")
	if jwt != "" {
		req.Header.Set("Authorization", "Bearer "+jwt)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}
	resp, err := client.Do(req)
	if err != nil {
		return upstreamCallResult{}, fmt.Errorf("魔方财务调用失败：%w", err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	text := string(raw)
	bodyMap := decodeJSONObject(raw)
	payloadMap := extractPayload(bodyMap)
	return upstreamCallResult{
		StatusCode: resp.StatusCode,
		Text:       text,
		Body:       bodyMap,
		Payload:    payloadMap,
		TaskID:     extractTaskID(payloadMap),
	}, nil
}

func nestedFormValues(payload map[string]any) url.Values {
	values := url.Values{}
	var add func(string, any)
	add = func(prefix string, value any) {
		switch typed := value.(type) {
		case nil:
			values.Add(prefix, "")
		case map[string]any:
			for key, nested := range typed {
				add(prefix+"["+key+"]", nested)
			}
		case map[string]string:
			for key, nested := range typed {
				add(prefix+"["+key+"]", nested)
			}
		case []any:
			for _, nested := range typed {
				add(prefix+"[]", nested)
			}
		case []string:
			for _, nested := range typed {
				add(prefix+"[]", nested)
			}
		case []int:
			for _, nested := range typed {
				add(prefix+"[]", nested)
			}
		case bool:
			if typed {
				values.Add(prefix, "1")
			} else {
				values.Add(prefix, "0")
			}
		default:
			values.Add(prefix, fmt.Sprint(value))
		}
	}
	for key, value := range payload {
		add(key, value)
	}
	return values
}

func decryptStoredSecret(token string) (string, error) {
	raw, err := base64.StdEncoding.DecodeString(token)
	if err != nil {
		return "", err
	}
	hash := sha256.Sum256([]byte(os.Getenv("JWT_SECRET_KEY")))
	block, err := aes.NewCipher(hash[:])
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	nonceSize := gcm.NonceSize()
	if len(raw) <= nonceSize {
		return "", errors.New("ciphertext too short")
	}
	plain, err := gcm.Open(nil, raw[:nonceSize], raw[nonceSize:], nil)
	if err != nil {
		return "", err
	}
	if !utf8.Valid(plain) {
		return "", errors.New("plaintext is not utf8")
	}
	return string(plain), nil
}

func EncryptStoredSecret(value string) (string, error) {
	hash := sha256.Sum256([]byte(os.Getenv("JWT_SECRET_KEY")))
	block, err := aes.NewCipher(hash[:])
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", err
	}
	sealed := gcm.Seal(nonce, nonce, []byte(value), nil)
	return base64.StdEncoding.EncodeToString(sealed), nil
}

func dcimURL(server CloudServer, path string) string {
	if server.AccountType == "agent" {
		agentPath := strings.TrimPrefix(path, "/")
		if strings.Contains(agentPath, "?") {
			parts := strings.SplitN(agentPath, "?", 2)
			return baseURL(server) + "/index.php?path=" + parts[0] + "&" + parts[1]
		}
		return baseURL(server) + "/index.php?path=" + agentPath
	}
	return baseURL(server) + "/v1" + ensureSlash(path)
}

func baseURL(server CloudServer) string {
	scheme := "https"
	if !server.Secure {
		scheme = "http"
	}
	port := ""
	if server.Port != 0 && server.Port != 80 && server.Port != 443 {
		port = fmt.Sprintf(":%d", server.Port)
	}
	return fmt.Sprintf("%s://%s%s", scheme, strings.TrimRight(server.Hostname, "/"), port)
}

func ensureSlash(path string) string {
	if strings.HasPrefix(path, "/") {
		return path
	}
	return "/" + path
}

func isZJMFServer(server CloudServer) bool {
	return server.ServerType == "zjmf_finance"
}

func isDCIMServer(server CloudServer) bool {
	value := strings.ToLower(strings.TrimSpace(server.ServerType))
	return value == "dcimcloud" || value == "dcim_cloud" || value == "dcim"
}

func zjmfCloudSuffix(instance Instance, path string) string {
	if instance.UpstreamID == nil {
		return ""
	}
	prefix := fmt.Sprintf("/clouds/%d", *instance.UpstreamID)
	clean := strings.TrimRight(strings.SplitN(path, "?", 2)[0], "/")
	if clean == prefix {
		return ""
	}
	if strings.HasPrefix(clean, prefix+"/") {
		return clean[len(prefix):]
	}
	return strings.TrimRight(path, "/")
}

func decodeJSONObject(raw []byte) map[string]any {
	var out map[string]any
	if err := json.Unmarshal(raw, &out); err != nil {
		return map[string]any{}
	}
	return out
}

func extractPayload(body map[string]any) map[string]any {
	if data, ok := body["data"].(map[string]any); ok {
		return data
	}
	if body != nil {
		return body
	}
	return map[string]any{}
}

func extractTaskID(payload map[string]any) *int64 {
	for _, key := range []string{"taskid", "task_id", "id"} {
		if value, ok := int64FromAny(payload[key]); ok {
			return &value
		}
	}
	return nil
}

func resolveUpstreamTaskStatus(task map[string]any) string {
	if text, ok := stringFromAny(task["status"]); ok {
		switch strings.ToLower(text) {
		case "success", "finished", "completed", "done":
			return "success"
		case "failed", "failure", "error", "cancelled", "canceled":
			return "failed"
		default:
			return "running"
		}
	}
	if numeric, ok := int64FromAny(task["status"]); ok {
		switch numeric {
		case 2:
			return "success"
		case 3, 4, 5, -1:
			return "failed"
		}
	}
	if progress, ok := int64FromAny(task["progress"]); ok && progress >= 100 {
		return "success"
	}
	return "running"
}

func zjmfSuccess(body map[string]any) bool {
	if len(body) == 0 {
		return false
	}
	status, ok := int64FromAny(body["status"])
	if !ok {
		return false
	}
	return status == 200 || status == 1001
}

func upstreamMessage(body map[string]any, text string) string {
	for _, key := range []string{"msg", "message", "error", "status"} {
		if value := strings.TrimSpace(fmt.Sprint(body[key])); value != "" && value != "<nil>" {
			return value
		}
	}
	return trimText(text, 200)
}

func firstString(values ...any) string {
	for _, value := range values {
		text := strings.TrimSpace(fmt.Sprint(value))
		if text != "" && text != "<nil>" {
			return text
		}
	}
	return ""
}

func trimText(value string, n int) string {
	value = strings.TrimSpace(value)
	if len(value) <= n {
		return value
	}
	return value[:n]
}
