package commerce

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"gorm.io/gorm"
)

const maxSecurityGroups = 5

var securityGroupOrderFields = map[string]bool{"id": true, "name": true, "uid": true, "cloud_num": true, "rule_num": true}
var securityGroupTypes = map[string]bool{"host": true, "lightHost": true, "hyperv": true, "adsl": true}
var securityGroupRuleProtocols = map[string]bool{
	"all": true, "all_tcp": true, "all_udp": true, "tcp": true, "udp": true, "icmp": true,
	"ssh": true, "telnet": true, "http": true, "https": true, "mssql": true, "oracle": true,
	"mysql": true, "rdp": true, "postgresql": true, "redis": true, "gre": true,
}

type SecurityGroupCreateRequest struct {
	Name              string `json:"name"`
	HostID            *int64 `json:"hostid"`
	Type              string `json:"type"`
	CreateDefaultRule int    `json:"create_default_rule"`
}

type SecurityGroupUpdateRequest struct {
	Name string `json:"name"`
}

type SecurityGroupRuleRequest struct {
	Description *string `json:"description"`
	Direction   string  `json:"direction"`
	Protocol    string  `json:"protocol"`
	Port        string  `json:"port"`
	IP          string  `json:"ip"`
	Lock        int     `json:"lock"`
	StartIP     *string `json:"start_ip"`
	EndIP       *string `json:"end_ip"`
	StartPort   *int    `json:"start_port"`
	EndPort     *int    `json:"end_port"`
	Priority    *int    `json:"priority"`
	Action      *string `json:"action"`
}

type SecurityGroupLinkRequest struct {
	InstanceID int64 `json:"instance_id"`
	Type       int   `json:"type"`
}

func (s *Service) ListSecurityGroups(ctx context.Context, userID int64, query url.Values) (map[string]any, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	page := queryInt(query.Get("page"), 1, 1, 100000)
	perPage := queryInt(query.Get("per_page"), 20, 1, 100)
	orderBy := defaultString(query.Get("orderby"), "id")
	if !securityGroupOrderFields[orderBy] {
		return nil, BadRequest("排序字段无效")
	}
	sortDir := defaultString(query.Get("sort"), "desc")
	if sortDir != "asc" && sortDir != "desc" {
		return nil, BadRequest("排序方向无效")
	}
	securityType := strings.TrimSpace(query.Get("type"))
	if securityType != "" && !securityGroupTypes[securityType] {
		return nil, BadRequest("安全组类型无效")
	}

	clientServer, err := s.securityGroupClientServer(ctx, userID, query.Get("instance_id"))
	if err != nil {
		return nil, err
	}
	items, err := s.fetchUserSecurityGroups(ctx, clientServer.server, user, securityType)
	if err != nil {
		return nil, err
	}
	if search := strings.ToLower(strings.TrimSpace(query.Get("search"))); search != "" {
		filtered := items[:0]
		for _, item := range items {
			if strings.Contains(strings.ToLower(firstString(item["name"])), search) {
				filtered = append(filtered, item)
			}
		}
		items = filtered
	}
	sortSecurityGroups(items, orderBy, sortDir == "desc")
	total := len(items)
	pageItems := items
	totalPage := 0
	if query.Get("list_type") != "all" {
		if total > 0 {
			totalPage = (total + perPage - 1) / perPage
		}
		start := (page - 1) * perPage
		if start >= len(items) {
			pageItems = []map[string]any{}
		} else {
			end := start + perPage
			if end > len(items) {
				end = len(items)
			}
			pageItems = items[start:end]
		}
	} else if total > 0 {
		totalPage = 1
	}
	return map[string]any{
		"list": mapSecurityGroups(pageItems),
		"meta": map[string]any{"total": total, "total_page": totalPage, "page": page, "per_page": perPage},
	}, nil
}

func (s *Service) GetSecurityGroup(ctx context.Context, userID int64, groupID int64, allRules bool) (map[string]any, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	server, err := s.defaultSecurityCloudServer(ctx)
	if err != nil {
		return nil, err
	}
	item, err := s.fetchOwnedSecurityGroup(ctx, server, user, groupID, allRules)
	if err != nil {
		return nil, err
	}
	return publicSecurityGroupDetail(item), nil
}

func (s *Service) CreateSecurityGroup(ctx context.Context, userID int64, req SecurityGroupCreateRequest) (map[string]any, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	securityType := defaultString(req.Type, "host")
	if !securityGroupTypes[securityType] {
		return nil, BadRequest("安全组类型无效")
	}
	name := strings.TrimSpace(req.Name)
	if name == "" {
		return nil, BadRequest("请输入安全组名称")
	}
	if len([]rune(name)) > 64 {
		return nil, BadRequest("安全组名称不能超过 64 个字符")
	}

	var instance *Instance
	var product *Product
	var server CloudServer
	if req.HostID != nil {
		inst, prod, srv, err := s.securityGroupInstanceContext(ctx, userID, *req.HostID)
		if err != nil {
			return nil, err
		}
		instance, product, server = &inst, prod, srv
	} else {
		server, err = s.defaultSecurityCloudServer(ctx)
		if err != nil {
			return nil, err
		}
	}
	if server.ClientID == nil {
		return nil, BadRequest("云节点未配置魔方云用户ID，无法创建安全组")
	}
	existing, err := s.fetchUserSecurityGroups(ctx, server, user, "")
	if err != nil {
		return nil, err
	}
	if len(existing) >= maxSecurityGroups {
		return nil, BadRequest(fmt.Sprintf("每个用户最多创建 %d 个安全组", maxSecurityGroups))
	}
	payload := map[string]any{
		"name":                name,
		"description":         securityGroupCustomerDescription(user),
		"uid":                 *server.ClientID,
		"type":                securityType,
		"create_default_rule": req.CreateDefaultRule,
	}
	if instance != nil && instance.UpstreamID != nil {
		payload["hostid"] = *instance.UpstreamID
	}
	if product != nil && product.APIType == "dcim_cloud_rid" && product.APIIdentifier != nil {
		payload["rid"] = *product.APIIdentifier
	}
	result, err := callDCIMPath(ctx, server, "/security_groups", "POST", payload, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return map[string]any{"id": firstNonNil(result.Payload["id"], result.Body["id"])}, nil
}

func (s *Service) UpdateSecurityGroup(ctx context.Context, userID int64, groupID int64, req SecurityGroupUpdateRequest) (map[string]any, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	server, err := s.defaultSecurityCloudServer(ctx)
	if err != nil {
		return nil, err
	}
	if _, err := s.fetchOwnedSecurityGroup(ctx, server, user, groupID, false); err != nil {
		return nil, err
	}
	name := strings.TrimSpace(req.Name)
	if name == "" {
		return nil, BadRequest("请输入安全组名称")
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_groups/%d", groupID), "PUT", map[string]any{
		"name":        name,
		"description": securityGroupCustomerDescription(user),
	}, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	payload := result.Payload
	if len(payload) == 0 {
		payload = map[string]any{}
	}
	payload["id"] = firstNonNil(payload["id"], groupID)
	payload["name"] = firstNonNil(payload["name"], name)
	return publicSecurityGroupItem(payload), nil
}

func (s *Service) DeleteSecurityGroup(ctx context.Context, userID int64, groupID int64) (map[string]any, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	server, err := s.defaultSecurityCloudServer(ctx)
	if err != nil {
		return nil, err
	}
	if server.ClientID == nil {
		return nil, BadRequest("云节点未配置魔方云用户ID，无法删除安全组")
	}
	if _, err := s.fetchOwnedSecurityGroup(ctx, server, user, groupID, false); err != nil {
		return nil, err
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_groups/%d?uid=%d", groupID, *server.ClientID), "DELETE", nil, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return result.Payload, nil
}

func (s *Service) LinkSecurityGroup(ctx context.Context, userID int64, groupID int64, req SecurityGroupLinkRequest) (map[string]any, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	instance, err := s.userInstance(ctx, userID, req.InstanceID)
	if err != nil {
		return nil, err
	}
	if instance.UpstreamID == nil {
		return nil, BadRequest("实例尚未开通，无法关联安全组")
	}
	if instance.CloudServerID == nil {
		return nil, BadRequest("实例未绑定云节点，无法关联安全组")
	}
	server, err := s.cloudServer(ctx, *instance.CloudServerID)
	if err != nil {
		return nil, err
	}
	if isZJMFServer(server) {
		return nil, BadRequest("对接产品不支持该操作")
	}
	if _, err := s.fetchOwnedSecurityGroup(ctx, server, user, groupID, false); err != nil {
		return nil, err
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_groups/%d/links", groupID), "POST", map[string]any{
		"type":    req.Type,
		"cloud[]": *instance.UpstreamID,
	}, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return result.Payload, nil
}

func (s *Service) UnlinkSecurityGroup(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	if instance.UpstreamID == nil {
		return nil, BadRequest("实例尚未开通，无法解除安全组关联")
	}
	if instance.CloudServerID == nil {
		return nil, BadRequest("实例未绑定云节点，无法解除安全组关联")
	}
	server, err := s.cloudServer(ctx, *instance.CloudServerID)
	if err != nil {
		return nil, err
	}
	if isZJMFServer(server) {
		return nil, BadRequest("对接产品不支持该操作")
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/clouds/%d/security_groups", *instance.UpstreamID), "DELETE", nil, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return result.Payload, nil
}

func (s *Service) CreateSecurityGroupRule(ctx context.Context, userID int64, groupID int64, req SecurityGroupRuleRequest) (map[string]any, error) {
	user, server, err := s.securityGroupUserAndDefaultServer(ctx, userID)
	if err != nil {
		return nil, err
	}
	if server.ClientID == nil {
		return nil, BadRequest("云节点未配置魔方云用户ID，无法添加规则")
	}
	if _, err := s.fetchOwnedSecurityGroup(ctx, server, user, groupID, false); err != nil {
		return nil, err
	}
	payload, err := securityGroupRulePayload(req)
	if err != nil {
		return nil, err
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_groups/%d/rules?uid=%d", groupID, *server.ClientID), "POST", payload, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return result.Payload, nil
}

func (s *Service) UpdateSecurityGroupRule(ctx context.Context, userID int64, ruleID int64, req SecurityGroupRuleRequest) (map[string]any, error) {
	user, server, err := s.securityGroupUserAndDefaultServer(ctx, userID)
	if err != nil {
		return nil, err
	}
	if _, _, err := s.findOwnedSecurityGroupRule(ctx, server, user, ruleID); err != nil {
		return nil, err
	}
	payload, err := securityGroupRulePayload(req)
	if err != nil {
		return nil, err
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_group_rules/%d", ruleID), "PUT", payload, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return result.Payload, nil
}

func (s *Service) DeleteSecurityGroupRule(ctx context.Context, userID int64, ruleID int64) (map[string]any, error) {
	user, server, err := s.securityGroupUserAndDefaultServer(ctx, userID)
	if err != nil {
		return nil, err
	}
	if server.ClientID == nil {
		return nil, BadRequest("云节点未配置魔方云用户ID，无法删除规则")
	}
	if _, _, err := s.findOwnedSecurityGroupRule(ctx, server, user, ruleID); err != nil {
		return nil, err
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_group_rules/%d?uid=%d", ruleID, *server.ClientID), "DELETE", nil, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	return result.Payload, nil
}

func (s *Service) ListSecurityGroupRuleProtocols(ctx context.Context, userID int64) ([]map[string]any, error) {
	if _, err := s.securityGroupUser(ctx, userID); err != nil {
		return nil, err
	}
	server, err := s.defaultSecurityCloudServer(ctx)
	if err != nil {
		return nil, err
	}
	result, err := callDCIMPath(ctx, server, "/security_group_rule_protocols", "GET", nil, 10*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	list := anyList(firstNonNil(result.Body["data"], result.Payload["data"], result.Payload))
	out := make([]map[string]any, 0, len(list))
	for _, raw := range list {
		item, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		out = append(out, map[string]any{
			"name":  firstString(item["name"], item["value"]),
			"value": firstString(item["value"]),
			"port":  firstString(item["port"]),
		})
	}
	return out, nil
}

type securityClientServer struct {
	server CloudServer
}

func (s *Service) securityGroupClientServer(ctx context.Context, userID int64, instanceIDRaw string) (securityClientServer, error) {
	if strings.TrimSpace(instanceIDRaw) == "" {
		server, err := s.defaultSecurityCloudServer(ctx)
		return securityClientServer{server: server}, err
	}
	instanceID, err := strconv.ParseInt(instanceIDRaw, 10, 64)
	if err != nil || instanceID <= 0 {
		return securityClientServer{}, BadRequest("实例不存在")
	}
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return securityClientServer{}, err
	}
	if instance.CloudServerID == nil {
		return securityClientServer{server: CloudServer{}}, BadRequest("实例未绑定云节点")
	}
	server, err := s.cloudServer(ctx, *instance.CloudServerID)
	return securityClientServer{server: server}, err
}

func (s *Service) securityGroupUserAndDefaultServer(ctx context.Context, userID int64) (UserAccount, CloudServer, error) {
	user, err := s.securityGroupUser(ctx, userID)
	if err != nil {
		return UserAccount{}, CloudServer{}, err
	}
	server, err := s.defaultSecurityCloudServer(ctx)
	if err != nil {
		return UserAccount{}, CloudServer{}, err
	}
	return user, server, nil
}

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

func (s *Service) defaultSecurityCloudServer(ctx context.Context) (CloudServer, error) {
	var server CloudServer
	err := s.db.WithContext(ctx).
		Where("disabled = ? AND client_id IS NOT NULL", false).
		Order("sort_order ASC, id ASC").
		First(&server).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return CloudServer{}, BadRequest("未配置魔方云用户ID，无法管理安全组")
	}
	return server, err
}

func (s *Service) cloudServer(ctx context.Context, id int64) (CloudServer, error) {
	var server CloudServer
	if err := s.db.WithContext(ctx).Where("id = ?", id).First(&server).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return CloudServer{}, BadRequest("云节点不存在")
		}
		return CloudServer{}, err
	}
	if server.Disabled {
		return CloudServer{}, BadRequest("云节点已禁用")
	}
	return server, nil
}

func (s *Service) securityGroupInstanceContext(ctx context.Context, userID int64, instanceID int64) (Instance, *Product, CloudServer, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return Instance{}, nil, CloudServer{}, err
	}
	if instance.UpstreamID == nil {
		return Instance{}, nil, CloudServer{}, BadRequest("实例尚未开通，无法关联安全组")
	}
	if instance.CloudServerID == nil {
		return Instance{}, nil, CloudServer{}, BadRequest("实例未绑定云节点，无法创建安全组")
	}
	server, err := s.cloudServer(ctx, *instance.CloudServerID)
	if err != nil {
		return Instance{}, nil, CloudServer{}, err
	}
	if isZJMFServer(server) {
		return Instance{}, nil, CloudServer{}, BadRequest("对接产品不支持该操作")
	}
	var product Product
	productPtr := (*Product)(nil)
	if err := s.db.WithContext(ctx).Where("id = ?", instance.ProductID).First(&product).Error; err == nil {
		productPtr = &product
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return Instance{}, nil, CloudServer{}, err
	}
	return instance, productPtr, server, nil
}

func (s *Service) fetchUserSecurityGroups(ctx context.Context, server CloudServer, user UserAccount, securityType string) ([]map[string]any, error) {
	marker := securityGroupCustomerMarker(user)
	payload := map[string]any{"list_type": "all", "search": marker}
	if server.ClientID != nil {
		payload["user"] = *server.ClientID
	}
	if securityType != "" {
		payload["type"] = securityType
	}
	result, err := callDCIMPath(ctx, server, "/security_groups", "GET", payload, 10*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	list := anyList(firstNonNil(result.Body["data"], result.Payload["data"], result.Payload))
	out := make([]map[string]any, 0, len(list))
	for _, raw := range list {
		item, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		if !strings.Contains(firstString(item["description"]), marker) {
			continue
		}
		out = append(out, item)
	}
	return out, nil
}

func (s *Service) fetchOwnedSecurityGroup(ctx context.Context, server CloudServer, user UserAccount, groupID int64, allRules bool) (map[string]any, error) {
	getAll := 0
	if allRules {
		getAll = 1
	}
	result, err := callDCIMPath(ctx, server, fmt.Sprintf("/security_groups/%d", groupID), "GET", map[string]any{"get_all_rule": getAll}, 10*time.Second)
	if err != nil {
		return nil, err
	}
	if err := ensureUpstreamOK(result); err != nil {
		return nil, err
	}
	item := result.Payload
	if len(item) == 0 {
		item = result.Body
	}
	marker := securityGroupCustomerMarker(user)
	if !strings.Contains(firstString(item["description"]), marker) {
		return nil, NotFound("安全组不存在")
	}
	if server.ClientID != nil {
		if uid, ok := int64FromAny(item["uid"]); ok && uid != *server.ClientID {
			return nil, NotFound("安全组不存在")
		}
	}
	return item, nil
}

func (s *Service) findOwnedSecurityGroupRule(ctx context.Context, server CloudServer, user UserAccount, ruleID int64) (map[string]any, map[string]any, error) {
	groups, err := s.fetchUserSecurityGroups(ctx, server, user, "")
	if err != nil {
		return nil, nil, err
	}
	for _, group := range groups {
		groupID, ok := int64FromAny(group["id"])
		if !ok {
			continue
		}
		detail, err := s.fetchOwnedSecurityGroup(ctx, server, user, groupID, true)
		if err != nil {
			continue
		}
		for _, raw := range anyList(detail["rule"]) {
			rule, ok := raw.(map[string]any)
			if !ok {
				continue
			}
			if id, ok := int64FromAny(rule["id"]); ok && id == ruleID {
				return detail, rule, nil
			}
		}
	}
	return nil, nil, NotFound("安全组规则不存在")
}

func securityGroupRulePayload(req SecurityGroupRuleRequest) (map[string]any, error) {
	protocol := strings.ToLower(strings.TrimSpace(req.Protocol))
	if !securityGroupRuleProtocols[protocol] {
		return nil, BadRequest("规则协议无效")
	}
	if req.Direction != "in" && req.Direction != "out" {
		return nil, BadRequest("规则方向无效")
	}
	if !validRulePort(req.Port) {
		return nil, BadRequest("端口范围无效，请输入 22 或 1-65535 这样的格式")
	}
	if !validRuleIP(req.IP) {
		return nil, BadRequest("授权 IP 无效，请输入 0.0.0.0/0 这样的格式")
	}
	payload := map[string]any{
		"direction": req.Direction,
		"protocol":  protocol,
		"port":      strings.TrimSpace(req.Port),
		"ip":        strings.TrimSpace(req.IP),
		"lock":      req.Lock,
	}
	if req.Description != nil {
		payload["description"] = strings.TrimSpace(*req.Description)
	} else {
		payload["description"] = ""
	}
	if req.StartIP != nil && *req.StartIP != "" {
		payload["start_ip"] = *req.StartIP
	}
	if req.EndIP != nil && *req.EndIP != "" {
		payload["end_ip"] = *req.EndIP
	}
	if req.StartPort != nil {
		payload["start_port"] = *req.StartPort
	}
	if req.EndPort != nil {
		payload["end_port"] = *req.EndPort
	}
	if req.Priority != nil {
		payload["priority"] = *req.Priority
	}
	if req.Action != nil && *req.Action != "" {
		payload["action"] = *req.Action
	}
	return payload, nil
}

func validRulePort(value string) bool {
	parts := strings.Split(strings.TrimSpace(value), "-")
	if len(parts) < 1 || len(parts) > 2 {
		return false
	}
	nums := make([]int, 0, len(parts))
	for _, part := range parts {
		n, err := strconv.Atoi(part)
		if err != nil || n < 1 || n > 65535 {
			return false
		}
		nums = append(nums, n)
	}
	return len(nums) == 1 || nums[0] <= nums[1]
}

func validRuleIP(value string) bool {
	_, _, err := net.ParseCIDR(strings.TrimSpace(value))
	if err == nil {
		return true
	}
	return net.ParseIP(strings.TrimSpace(value)) != nil
}

func publicSecurityGroupItem(item map[string]any) map[string]any {
	return map[string]any{
		"id":          firstNonNil(item["id"], item["ID"]),
		"name":        firstString(item["name"]),
		"cloud_num":   intOrZero(item["cloud_num"]),
		"rule_num":    intOrZero(item["rule_num"]),
		"username":    firstString(item["username"]),
		"create_time": item["create_time"],
		"type":        defaultString(firstString(item["type"]), "host"),
	}
}

func publicSecurityGroupDetail(item map[string]any) map[string]any {
	rules := anyList(item["rule"])
	publicRules := make([]map[string]any, 0, len(rules))
	for _, raw := range rules {
		rule, ok := raw.(map[string]any)
		if !ok {
			continue
		}
		publicRules = append(publicRules, publicSecurityGroupRule(rule))
	}
	out := publicSecurityGroupItem(item)
	if len(publicRules) > 0 {
		out["rule_num"] = len(publicRules)
	}
	out["update_time"] = item["update_time"]
	out["rule"] = publicRules
	return out
}

func publicSecurityGroupRule(item map[string]any) map[string]any {
	return map[string]any{
		"id":          item["id"],
		"description": firstString(item["description"]),
		"direction":   firstString(item["direction"]),
		"protocol":    firstString(item["protocol"]),
		"port":        firstString(item["port"]),
		"ip":          firstString(item["ip"]),
		"lock":        intOrZero(item["lock"]),
		"start_ip":    item["start_ip"],
		"end_ip":      item["end_ip"],
		"start_port":  firstNonNil(intPtrFromAny(item["start_port"]), item["start_port"]),
		"end_port":    firstNonNil(intPtrFromAny(item["end_port"]), item["end_port"]),
		"priority":    firstNonNil(intPtrFromAny(item["priority"]), item["priority"]),
		"action":      item["action"],
		"create_time": item["create_time"],
	}
}

func mapSecurityGroups(items []map[string]any) []map[string]any {
	out := make([]map[string]any, 0, len(items))
	for _, item := range items {
		out = append(out, publicSecurityGroupItem(item))
	}
	return out
}

func sortSecurityGroups(items []map[string]any, orderBy string, desc bool) {
	sort.SliceStable(items, func(i, j int) bool {
		var less bool
		if orderBy == "name" {
			less = firstString(items[i]["name"]) < firstString(items[j]["name"])
		} else {
			less = intOrZero(items[i][orderBy]) < intOrZero(items[j][orderBy])
		}
		if desc {
			return !less
		}
		return less
	})
}

func securityGroupCustomerMarker(user UserAccount) string {
	return fmt.Sprintf("本地ID %d", user.ID)
}

func securityGroupCustomerDescription(user UserAccount) string {
	return fmt.Sprintf("客户：用户%d（%s）", user.ID, securityGroupCustomerMarker(user))
}

func ensureUpstreamOK(result upstreamCallResult) error {
	if result.StatusCode < 200 || result.StatusCode >= 300 {
		return HTTPError{Status: http.StatusBadGateway, Detail: fmt.Sprintf("上游返回异常（HTTP %d）：%s", result.StatusCode, trimText(result.Text, 200))}
	}
	return nil
}

func anyList(value any) []any {
	if value == nil {
		return []any{}
	}
	if list, ok := value.([]any); ok {
		return list
	}
	return []any{}
}

func intOrZero(value any) int {
	if id, ok := int64FromAny(value); ok {
		return int(id)
	}
	return 0
}

func intPtrFromAny(value any) *int {
	if id, ok := int64FromAny(value); ok {
		out := int(id)
		return &out
	}
	return nil
}
