# -*- coding: utf-8 -*-  # 指定文件编码为 UTF-8，确保中文注释不乱码。 
import os  # 导入 os 用于读取环境变量配置。 
import sys  # 导入 sys 用于返回主程序退出码。 
import time  # 导入 time 用于等待异步资源就绪。 
from typing import Optional, List, Dict, Tuple  # 导入类型标注便于阅读与提示。 
from volcengine import config, data  # 导入配置与数据参数用于资源命名与绑定。 
from volcengine.billing_service import get_available_balance  # 导入余额查询方法用于主流程判断。 
from volcengine.vpc_service import create_default_vpc, wait_for_vpc_available, find_vpc_by_name  # 导入 VPC 创建、查询与状态等待方法。 
from volcengine.subnet_service import create_default_subnet, find_subnet_by_name  # 导入子网创建与查询方法用于接入后续流程。 
from volcengine.security_group_service import (  # 导入安全组创建、规则授权与查询方法。 
    create_default_security_group, 
    apply_default_security_group_rules, 
    find_security_group_by_name, 
) 
from volcengine.ecs_service import (  # 导入 ECS 创建、运行/网卡等待与查询方法。 
    create_default_instance, 
    wait_for_instance_network_interfaces, 
    wait_for_instance_running, 
    find_instance_by_name, 
    find_instance_by_private_ip, 
) 
from volcengine.nic_service import assign_private_ips, cleanup_orphan_network_interfaces_by_private_ips, describe_network_interfaces  # 导入网卡私有 IP 绑定与查询方法。 
from volcengine.eip_service import allocate_eip_batch, associate_eip_address, describe_eip_addresses, release_eip_address  # 导入公网 IP 申请、查询、绑定与释放方法。 

try: 
    sys.stdout.reconfigure(encoding="utf-8", errors="replace") 
    sys.stderr.reconfigure(encoding="utf-8", errors="replace") 
except Exception: 
    pass 

REGION_ALIAS = {  # 兼容前端短名称与火山地域代码的映射。 
    "beijing": "cn-beijing", 
    "shanghai": "cn-shanghai", 
    "guangzhou": "cn-guangzhou", 
    "bj": "cn-beijing", 
    "sh": "cn-shanghai", 
    "gz": "cn-guangzhou", 
} 
REGION_LABELS = {  # 地域中文展示名称，便于前端展示。 
    "cn-beijing": "北京", 
    "cn-shanghai": "上海", 
    "cn-guangzhou": "广州", 
} 

def _normalize_region(value: str) -> str:  # 标准化地域值，确保符合火山 SDK 规范。 
    cleaned = (value or "").strip()  # 清理空白。 
    if not cleaned:  # 空值直接返回。 
        return "" 
    lowered = cleaned.lower()  # 统一使用小写进行匹配。 
    return REGION_ALIAS.get(lowered, cleaned)  # 映射短名称到标准地域。 

def _region_label(region: str) -> str:  # 将地域代码映射为中文名称。 
    region_value = _normalize_region(region)  # 先标准化地域代码。 
    return REGION_LABELS.get(region_value, region_value or "未知")  # 返回中文名称或原始值。 

def _summary_tag(region: str, group_index: int) -> str:  # 生成地区输出卡片的唯一标签。 
    suffix = "one" if group_index == 1 else "two"  # 组名 One/Two。 
    region_value = _normalize_region(region)  # 使用标准化后的地域。 
    return f"{region_value}-{suffix}"  # 拼接成唯一标签。 


def _region_group_label(region: str, group_index: Optional[int] = None) -> str:
    label = _region_label(region)
    return f"{label}{group_index}" if group_index and group_index >= 2 else label


def _print_region_step(region: str, message: str, group_index: Optional[int] = None) -> None:
    """按实际执行阶段输出简短的地区进度。"""
    server_prefix = f"第{group_index}台云服务器：" if group_index else ""
    print(f"[{_region_group_label(region, group_index)}] 步骤: {server_prefix}{message}", flush=True)


def _print_region_message(region: str, message: str, group_index: Optional[int] = None) -> None:
    print(f"[{_region_group_label(region, group_index)}] {message}", flush=True)

def _collect_regions(region: Optional[str]) -> List[str]:  # 汇总需要检测的地域列表。 
    region_value = _normalize_region(region or "")  # 清理并标准化传入地域。 
    if region_value:  # 若传入地域不为空则直接使用。 
        return [region_value]  # 返回单一地域列表。 
    candidates: List[str] = []  # 准备候选列表。 
    for value in (  # 依次尝试默认地域与常用地域。 
        getattr(config, "DEFAULT_REGION", ""), 
        getattr(config, "BJ", ""), 
        getattr(config, "SH", ""), 
        getattr(config, "GZ", ""), 
    ): 
        cleaned = _normalize_region(str(value))  # 清理并标准化候选地域字符串。 
        if cleaned and cleaned not in candidates:  # 去除空值与重复项。 
            candidates.append(cleaned)  # 保存候选地域。 
    return candidates  # 返回候选地域列表。 

def _parse_int(value: str, default_value: int) -> int:  # 安全解析整数配置。 
    try:  # 捕获转换异常。 
        return int(str(value).strip())  # 返回转换后的整数。 
    except (TypeError, ValueError):  # 转换失败时使用默认值。 
        return default_value  # 返回默认值。 


def _new_ip_only_mode() -> bool:
    return str(os.getenv("VOLC_NEW_IP_ONLY") or "").strip().lower() in {"1", "true", "yes", "on"}


def _used_ip_filter() -> set:
    return {
        value.strip()
        for value in str(os.getenv("VOLC_USED_IP_FILTER") or "").split(",")
        if value.strip()
    }

def _parse_region_counts(region_override: Optional[str]) -> Dict[str, int]:  # 解析地域与服务器数量配置。 
    default_count = _parse_int(os.getenv("VOLC_SERVER_COUNT", ""), data.ECS_INSTANCE_COUNT or 1)  # 默认单地区数量。 
    default_count = 1 if default_count < 1 else default_count  # 防止默认值小于 1。 
    region_counts: Dict[str, int] = {}  # 保存地域数量映射。 
    env_counts = (os.getenv("VOLC_REGION_COUNTS") or "").strip()  # 读取形如 region=count 的配置。 
    if env_counts:  # 优先解析带数量的配置。 
        for raw in env_counts.split(","):  # 逐段解析。 
            item = raw.strip()  # 清理空格。 
            if not item:  # 跳过空项。 
                continue 
            if "=" in item:  # 兼容 region=count 格式。 
                region_value, count_value = item.split("=", 1) 
            else:  # 未指定数量则用默认值。 
                region_value, count_value = item, "" 
            region_value = _normalize_region(region_value)  # 清理并标准化地域。 
            if not region_value:  # 跳过空地域。 
                continue 
            count = _parse_int(count_value, default_count)  # 解析数量。 
            count = 1 if count < 1 else count  # 最少 1 台。 
            count = 2 if count > 2 else count  # 火山流程仅支持两组命名。 
            region_counts[region_value] = count  # 写入映射。 
    if not region_counts:  # 若未配置数量映射则尝试读取地域列表。 
        env_regions = (os.getenv("VOLC_REGIONS") or "").strip()  # 读取地域列表配置。 
        if env_regions:  # 有配置时按地域列表组装。 
            for raw in env_regions.split(","): 
                region_value = _normalize_region(raw) 
                if region_value: 
                    region_counts[region_value] = default_count 
    if not region_counts:  # 若仍为空则使用默认地域。 
        for region_value in _collect_regions(region_override): 
            region_counts[region_value] = default_count 
    return region_counts  # 返回最终地域数量映射。 

def _parse_region_groups() -> Dict[str, set]:  # 解析前端明确选择的地区/地区2分组。
    region_groups: Dict[str, set] = {}
    env_groups = (os.getenv("VOLC_REGION_GROUPS") or "").strip()
    if not env_groups:
        return region_groups
    for raw in env_groups.split(","):
        item = raw.strip()
        if not item or "=" not in item:
            continue
        region_value, group_values = item.split("=", 1)
        region_value = _normalize_region(region_value)
        if not region_value:
            continue
        parsed_groups = {
            group_index
            for group_index in (_parse_int(value.strip(), 0) for value in group_values.split("|"))
            if group_index in {1, 2}
        }
        if parsed_groups:
            region_groups[region_value] = parsed_groups
    return region_groups

def _group_exists(region: str, group_index: int) -> bool:  # 判断指定组资源是否存在。 
    ecs_name = data.ECS_NAME_GROUP_2 if group_index == 2 else data.ECS_NAME_GROUP_1  # 选择实例名称。 
    ok, message, _ = find_instance_by_name(region, ecs_name)  # 查询实例。 
    if ok:  # 实例存在则认为组已存在。 
        return True 
    if message and "未找到" not in message:  # 仅在非“未找到”错误时提示。 
        _print_region_message(region, f"查询云服务器失败: {message}", group_index)
    return False  # 默认视为不存在。 

def _plan_groups_for_region(region: str, desired_count: int) -> List[int]:  # 根据已有资源规划需创建的分组。
    desired_count = 2 if desired_count >= 2 else 1  # 仅允许 1 或 2。
    group1_exists = _group_exists(region, 1)  # 检查第一组。
    group2_exists = _group_exists(region, 2)  # 检查第二组。
    if _new_ip_only_mode():
        if desired_count >= 2:
            planned = [group_index for group_index, exists in ((1, group1_exists), (2, group2_exists)) if exists]
        elif group1_exists:
            planned = [1]
        elif group2_exists:
            planned = [2]
        else:
            planned = []
        if not planned:
            _print_region_message(region, "所选地区没有可复用的云服务器，购买选择地区新IP不会创建新云服务器")
        return planned
    if desired_count >= 2:
        planned: List[int] = [1, 2]  # 满配模式仍然固定补齐两套。
    else:
        existing_eips = _load_existing_eips(region)
        group_ip_count = len(_build_ip_order())
        group1_complete = _group_resource_complete(region, 1, existing_eips, group_ip_count)
        group2_complete = _group_resource_complete(region, 2, existing_eips, group_ip_count)
        if group1_complete and group2_complete:
            _print_region_message(region, "已存在两套云服务器/公网IP，跳过本地区新购")
            return []
        if group1_complete and not group2_complete:
            _print_region_message(region, "当前地区资源已完整，本次规划购买地区2资源")
            planned = [2]
        elif group2_complete and not group1_complete:
            _print_region_message(region, "地区2资源已完整，本次规划购买当前地区资源")
            planned = [1]
        else:
            planned = [1]
    if group1_exists and 1 in planned:
        _print_region_message(region, "云服务器 wawacloud_ecs1 已存在，将复用并补齐后续资源")
    if group2_exists and 2 in planned:
        _print_region_message(region, "云服务器 wawacloud_ecs2 已存在，将复用并补齐后续资源", 2)
    return planned  # 返回待创建组列表。

def _build_ip_order() -> List[str]:  # 生成私网 IP 绑定顺序。
    ip_order: List[str] = []  # 按绑定顺序收集需要公网 IP 的私网地址。 
    if data.ECS_PRIMARY_IP:  # 先加入主网卡主 IP。 
        ip_order.append(data.ECS_PRIMARY_IP) 
    ip_order.extend([ip for ip in data.ECS_PRIMARY_EXTRA_IPS if ip])  # 加入主网卡附加 IP。 
    if data.ECS_SECONDARY_IP:  # 再加入副网卡主 IP。 
        ip_order.append(data.ECS_SECONDARY_IP) 
    ip_order.extend([ip for ip in data.ECS_SECONDARY_EXTRA_IPS if ip])  # 加入副网卡附加 IP。 
    return [ip for ip in ip_order if ip]  # 过滤空值避免申请数量错误。

def _balance_threshold_for_ip_count(total_ips: int) -> int:  # 只要需要购买公网 IP，就执行官方最低余额门槛。
    total_ips = max(0, int(total_ips or 0))
    if total_ips <= 0:
        return 0
    return max(100, int(getattr(config, "VOLC_BALANCE_CHECK_FULL", 100)))

def _eip_value(eip: Dict, *keys: str) -> str:  # 从公网 IP 字典中读取字段。 
    wanted = {str(key).lower() for key in keys if key} 
    for key in keys: 
        value = eip.get(key) 
        if value not in (None, ""): 
            return str(value).strip() 
    raw = eip.get("_raw") 
    if isinstance(raw, dict): 
        for key in keys: 
            value = raw.get(key) 
            if value not in (None, ""): 
                return str(value).strip() 
    def walk(value) -> str: 
        if isinstance(value, dict): 
            for item_key, item_value in value.items(): 
                if str(item_key).lower() in wanted and item_value not in (None, "") and not isinstance(item_value, (dict, list)): 
                    return str(item_value).strip() 
            for item_value in value.values(): 
                found = walk(item_value) 
                if found: 
                    return found 
        elif isinstance(value, list): 
            for item_value in value: 
                found = walk(item_value) 
                if found: 
                    return found 
        return "" 
    if wanted: 
        return walk(eip) 
    return "" 

def _normalize_eip_record(eip: Dict) -> Dict:  # 统一公网 IP 字段，便于复用已有资源。 
    return { 
        "allocation_id": _eip_value(eip, "AllocationId", "allocation_id", "EipId", "eip_id", "id"), 
        "eip_address": _eip_value(eip, "EipAddress", "eip_address", "IpAddress", "ip_address"), 
        "name": _eip_value(eip, "Name", "name", "EipName", "eip_name", "AllocationName", "allocation_name"),
        "associated_instance_id": _eip_value(
            eip,
            "AssociatedInstanceId",
            "associated_instance_id",
            "AssociatedInstanceID",
            "InstanceId",
            "instance_id",
            "NetworkInterfaceId",
            "network_interface_id",
            "EniId",
            "eni_id",
        ), 
        "private_ip_address": _eip_value(
            eip,
            "PrivateIpAddress",
            "private_ip_address",
            "PrivateIp",
            "private_ip",
            "AssociatedPrivateIpAddress",
            "associated_private_ip_address",
        ), 
        "associated_instance_type": _eip_value(
            eip,
            "AssociatedInstanceType",
            "associated_instance_type",
            "InstanceType",
            "instance_type",
            "AssociatedInstanceType",
        ), 
        "_raw": eip, 
    } 

def _load_existing_eips(region: str, *, log_count: bool = True) -> List[Dict]:  # 查询并规范化当前地域已有公网 IP。 
    ok, message, eips = describe_eip_addresses(region) 
    if not ok: 
        _print_region_message(region, f"查询已有公网 IP 失败，后续按需补买: {message}")
        return [] 
    records = [_normalize_eip_record(item) for item in eips] 
    records = [item for item in records if item.get("allocation_id")] 
    if log_count:
        _print_region_message(region, f"已有公网 IP 数量: {len(records)}")
    return records 

def _eip_is_bound(eip: Dict) -> bool:  # 判断公网 IP 是否已经绑定。 
    return bool(
        _eip_value(
            eip,
            "associated_instance_id",
            "AssociatedInstanceId",
            "AssociatedInstanceID",
            "InstanceId",
            "instance_id",
            "NetworkInterfaceId",
            "network_interface_id",
            "EniId",
            "eni_id",
        )
    ) 

def _eip_group_index(eip: Dict) -> int:  # 根据固定名称识别公网 IP 所属的第一套/第二套资源。
    name = _eip_value(eip, "name", "Name", "EipName", "eip_name", "AllocationName", "allocation_name").strip().lower()
    if name.startswith(str(data.EIP_NAME_PREFIX_GROUP_2).strip().lower()):
        return 2
    if name.startswith(str(data.EIP_NAME_PREFIX_GROUP_1).strip().lower()):
        return 1
    return 0


def _select_existing_eips_for_group(
    existing_eips: List[Dict],
    used_unbound_ids: set,
    group_size: int,
    group_index: int,
) -> List[Dict]:  # 为当前分组选择可复用公网 IP。
    selected: List[Dict] = []
    unbound_count = 0
    expected_group = 2 if int(group_index or 1) >= 2 else 1
    for eip in existing_eips:
        allocation_id = _eip_value(eip, "allocation_id", "AllocationId", "EipId", "eip_id", "id")
        if not allocation_id: 
            continue 
        if _eip_is_bound(eip): 
            # 已绑定 EIP 只能在后续绑定确认中按目标实例/网卡复核，不能作为下一组可用 EIP，
            # 否则 48 条第二组会误拿第一组已绑定 IP，导致可用数量判断重复。
            continue 
        if _eip_group_index(eip) != expected_group:
            continue
        if allocation_id in used_unbound_ids: 
            continue 
        if unbound_count >= group_size: 
            continue 
        selected.append(eip)
        used_unbound_ids.add(allocation_id)
        unbound_count += 1
    return selected

def _group_resource_complete(region: str, group_index: int, existing_eips: List[Dict], group_ip_count: int) -> bool:  # 判断一套 ECS/EIP 是否完整可视为正在运行。
    _, _, _, ecs_name = _resource_names(group_index)
    ok, message, instance = find_instance_by_name(region, ecs_name)
    if not ok:
        if message and "未找到" not in message:
            _print_region_message(region, f"查询云服务器 {ecs_name} 失败: {message}", group_index)
        return False
    status = str(instance.get("Status") or instance.get("status") or "").strip()
    lowered_status = status.lower()
    if lowered_status in {"deleted", "deleting", "stopped", "stopping"} or "删除" in status or "停止" in status:
        _print_region_message(region, f"云服务器 {ecs_name} 状态为 {status or '未知'}，不按完整运行资源复用", group_index)
        return False
    if group_ip_count <= 0:
        return True
    instance_id = _extract_id(instance, "InstanceId", "instance_id", "id")
    if not instance_id:
        return False
    nic_ok, nic_message, nic_list = describe_network_interfaces(region)
    if not nic_ok:
        _print_region_message(region, f"查询云服务器网卡归属失败，不按完整资源复用: {nic_message}", group_index)
        return False
    target_ids = {instance_id}
    for nic in nic_list:
        attached_instance_id = _extract_id(
            nic,
            "InstanceId",
            "instance_id",
            "AttachedInstanceId",
            "attached_instance_id",
            "AttachedInstanceID",
        )
        if attached_instance_id != instance_id:
            continue
        nic_id = _extract_id(nic, "NetworkInterfaceId", "network_interface_id", "EniId", "eni_id", "id")
        if nic_id:
            target_ids.add(nic_id)
    expected_group = 2 if int(group_index or 1) >= 2 else 1
    matched_count = 0
    for eip in existing_eips:
        if not _eip_is_bound(eip):
            continue
        bound_target_id = _eip_value(
            eip,
            "associated_instance_id",
            "AssociatedInstanceId",
            "AssociatedInstanceID",
            "InstanceId",
            "instance_id",
        )
        if bound_target_id not in target_ids:
            continue
        eip_group = _eip_group_index(eip)
        if eip_group and eip_group != expected_group:
            continue
        matched_count += 1
    return matched_count >= group_ip_count

def _resource_names(group_index: int) -> Tuple[str, str, str, str]:  # 返回指定组的资源名称。
    if group_index == 2:  # 第二组资源名称。
        return data.VPC_NAME_GROUP_2, data.SUBNET_NAME_GROUP_2, data.SECURITY_GROUP_NAME_GROUP_2, data.ECS_NAME_GROUP_2
    return data.VPC_NAME_GROUP_1, data.SUBNET_NAME_GROUP_1, data.SECURITY_GROUP_NAME_GROUP_1, data.ECS_NAME_GROUP_1

def _extract_id(item: Dict, *keys: str) -> str:  # 从 SDK 字典中按多个候选键提取 ID。 
    for key in keys:  # 逐个候选键读取。 
        value = item.get(key)  # 读取字段。 
        if value:  # 找到非空值则返回。 
            return str(value) 
    return ""  # 没有可用值时返回空字符串。 

def _is_private_ip_conflict(message: str) -> bool:  # 判断是否固定私网 IP 已被占用。 
    lowered = (message or "").lower() 
    return (
        "invalidprivateipaddresses.conflict" in lowered
        or "privateipaddresses already exists" in lowered
        or "private ip" in lowered and "already" in lowered
        or "已存在" in (message or "") and "私网" in (message or "")
    ) 

def _friendly_ecs_create_failure(message: str) -> str:
    lowered = str(message or "").lower()
    if "insufficient.balance" in lowered or "lack of balance" in lowered:
        return "云服务器创建失败：火山引擎返回账户可支付余额不足。"
    if _is_private_ip_conflict(message):
        return "云服务器创建失败：固定私网IP仍被残留网卡占用。"
    if "invalideni.inuse" in lowered:
        return "云服务器创建失败：目标网卡正在使用中，请稍后重试。"
    if "invalidinstance.inuse" in lowered:
        return "云服务器创建失败：目标云服务器正在执行其他操作，请稍后重试。"
    return "云服务器创建失败：火山引擎接口拒绝了创建请求，请稍后重试。"


def _is_not_found_message(message: str) -> bool:
    lowered = (message or "").lower()
    return "未找到" in (message or "") or "不存在" in (message or "") or "not found" in lowered or "notfound" in lowered

def _find_existing_instance_by_network(region: str, vpc_id: str, subnet_id: str) -> Tuple[bool, str, str]: 
    last_message = "" 
    for private_ip in _build_ip_order(): 
        ok, message, instance = find_instance_by_private_ip(region, vpc_id, subnet_id, private_ip) 
        if not ok: 
            last_message = message 
            continue 
        instance_id = _extract_id(instance, "InstanceId", "instance_id", "id") 
        if not instance_id: 
            return False, "按私网 IP 找到实例，但 InstanceId 为空", "" 
        return True, message, instance_id 
    return False, last_message or "未找到占用固定私网 IP 的实例", "" 

def _cleanup_orphan_fixed_ip_nics(
    region: str,
    vpc_id: str,
    subnet_id: str,
    stage: str,
    group_index: Optional[int] = None,
) -> Tuple[bool, int]:
    ok, message, info = cleanup_orphan_network_interfaces_by_private_ips(region, vpc_id, subnet_id, _build_ip_order()) 
    deleted_count = int(info.get("deleted_count") or 0) if isinstance(info, dict) else 0 
    if not ok: 
        _print_region_message(region, f"{stage}: {message}", group_index)
        return False, deleted_count 
    if deleted_count: 
        names = info.get("deleted_names") if isinstance(info, dict) else [] 
        name_text = "、".join(str(item) for item in names if item) 
        if name_text: 
            _print_region_message(region, f"{stage}: 已清理占用固定私网 IP 的残留网卡 {name_text}，继续复用网络资源", group_index)
        else: 
            _print_region_message(region, f"{stage}: 已清理 {deleted_count} 个占用固定私网 IP 的残留网卡，继续复用网络资源", group_index)
        eip_count = int(info.get("eip_disassociated_count") or 0) if isinstance(info, dict) else 0 
        if eip_count: 
            _print_region_message(region, f"{stage}: 已解绑残留网卡上的 {eip_count} 个公网 IP，后续按可复用公网 IP 处理", group_index)
    return True, deleted_count 

def _cleanup_orphan_fixed_ip_nics_before_eip_plan(region: str, group_index: int) -> bool: 
    vpc_name, subnet_name, _, _ = _resource_names(group_index) 
    ok, message, vpc = find_vpc_by_name(region, vpc_name) 
    if not ok: 
        if message and not _is_not_found_message(message): 
            _print_region_message(region, f"购买前固定私网 IP 检查: 查询 VPC 失败: {message}", group_index)
            return False 
        return True 
    vpc_id = _extract_id(vpc, "VpcId", "vpc_id") 
    if not vpc_id: 
        _print_region_message(region, "购买前固定私网 IP 检查: 已有 VPC 但资源标识为空", group_index)
        return False 
    ok, message, subnet = find_subnet_by_name(region, subnet_name) 
    if not ok: 
        if message and not _is_not_found_message(message): 
            _print_region_message(region, f"购买前固定私网 IP 检查: 查询子网失败: {message}", group_index)
            return False 
        return True 
    subnet_id = _extract_id(subnet, "SubnetId", "subnet_id") 
    if not subnet_id: 
        _print_region_message(region, "购买前固定私网 IP 检查: 已有子网但资源标识为空", group_index)
        return False 
    existing_ok, _, _ = _find_existing_instance_by_network(region, vpc_id, subnet_id) 
    if existing_ok: 
        _print_region_message(region, "购买前固定私网 IP 检查: 已有云服务器可复用，跳过残留网卡清理", group_index)
        return True 
    ok, _ = _cleanup_orphan_fixed_ip_nics(region, vpc_id, subnet_id, "购买前固定私网 IP 检查", group_index)
    return ok 

def _ensure_vpc(region: str, group_index: int) -> Tuple[bool, str, str]:  # 查询或创建 VPC。 
    vpc_name, _, _, _ = _resource_names(group_index)  # 获取 VPC 名称。 
    ok, message, existing = find_vpc_by_name(region, vpc_name)  # 先查询已有 VPC。 
    if ok:  # 找到已有 VPC 时复用。 
        vpc_id = _extract_id(existing, "VpcId", "vpc_id")  # 提取 VPC ID。 
        if not vpc_id:  # 同名资源缺少 ID 时停止。 
            return False, f"[{_region_group_label(region, group_index)}] 已存在 VPC {vpc_name}，但资源标识为空", ""
        _print_region_message(region, f"复用已有 VPC: {vpc_name}", group_index)
        wait_ok, wait_message = wait_for_vpc_available(region, vpc_id)  # 等待已有 VPC 可用。 
        if not wait_ok:  # VPC 不可用则停止。 
            return False, wait_message, "" 
        return True, "复用 VPC 成功", vpc_id 
    if message and "未找到" not in message:  # 查询失败且不是不存在。 
        return False, f"[{_region_group_label(region, group_index)}] 查询 VPC 失败: {message}", ""
    ok, message, vpc_data = create_default_vpc(region, group_index=group_index)  # 不存在才创建 VPC。 
    if not ok:  # 判断 VPC 创建是否失败。 
        return False, message, "" 
    result = vpc_data.get("Result") or vpc_data.get("result") or vpc_data  # 兼容不同响应结构。 
    vpc_id = _extract_id(result, "VpcId", "vpc_id")  # 提取 VPC ID。 
    if vpc_id:  # 若返回了 VPC ID 则打印明细。 
        _print_region_message(region, f"VPC 创建成功: {vpc_name}", group_index)
    if not vpc_id:  # 未返回 VPC ID 无法继续。 
        return False, f"[{_region_group_label(region, group_index)}] VPC 创建成功但资源标识为空", ""
    wait_ok, wait_message = wait_for_vpc_available(region, vpc_id)  # 等待 VPC 状态就绪后再继续。 
    if not wait_ok:  # 判断等待是否超时或失败。 
        return False, wait_message, "" 
    return True, "创建 VPC 成功", vpc_id 

def _ensure_subnet(region: str, vpc_id: str, group_index: int) -> Tuple[bool, str, str]:  # 查询或创建子网。 
    _, subnet_name, _, _ = _resource_names(group_index)  # 获取子网名称。 
    ok, message, existing = find_subnet_by_name(region, subnet_name, vpc_id=vpc_id)  # 查询已有子网。 
    if ok:  # 找到已有子网时复用。 
        subnet_id = _extract_id(existing, "SubnetId", "subnet_id")  # 提取子网 ID。 
        if not subnet_id:  # 同名资源缺少 ID 时停止。 
            return False, f"[{_region_group_label(region, group_index)}] 已存在子网 {subnet_name}，但资源标识为空", ""
        status = str(existing.get("Status") or existing.get("status") or "").strip().lower()  # 读取子网状态。 
        if status and status not in {"available", "active", "ok"}:  # 子网状态异常时停止。 
            return False, f"[{_region_group_label(region, group_index)}] 已存在子网 {subnet_name}，但状态异常: {status}", ""
        _print_region_message(region, f"复用已有子网: {subnet_name}", group_index)
        return True, "复用子网成功", subnet_id 
    if message and "未找到" not in message:  # 查询失败且不是不存在。 
        return False, f"[{_region_group_label(region, group_index)}] 查询子网失败: {message}", ""
    ok, message, subnet_data = create_default_subnet(region, vpc_id, group_index=group_index)  # 不存在才创建子网。 
    if not ok:  # 判断子网创建是否失败。 
        return False, message, "" 
    subnet_result = subnet_data.get("Result") or subnet_data.get("result") or subnet_data  # 兼容不同响应结构。 
    subnet_id = _extract_id(subnet_result, "SubnetId", "subnet_id")  # 提取子网 ID。 
    if subnet_id:  # 若返回了子网 ID 则打印。 
        _print_region_message(region, f"子网创建成功: {subnet_name}", group_index)
    if not subnet_id:  # 未返回子网 ID 无法继续。 
        return False, f"[{_region_group_label(region, group_index)}] 子网创建成功但资源标识为空", ""
    return True, "创建子网成功", subnet_id 

def _ensure_security_group(region: str, vpc_id: str, group_index: int) -> Tuple[bool, str, str]:  # 查询或创建安全组。 
    _, _, sg_name, _ = _resource_names(group_index)  # 获取安全组名称。 
    ok, message, existing = find_security_group_by_name(region, sg_name, vpc_id=vpc_id)  # 查询已有安全组。 
    if ok:  # 找到已有安全组时复用。 
        sg_id = _extract_id(existing, "SecurityGroupId", "security_group_id")  # 提取安全组 ID。 
        if not sg_id:  # 同名资源缺少 ID 时停止。 
            return False, f"[{_region_group_label(region, group_index)}] 已存在安全组 {sg_name}，但资源标识为空", ""
        _print_region_message(region, f"复用已有安全组: {sg_name}", group_index)
    else: 
        if message and "未找到" not in message:  # 查询失败且不是不存在。 
            return False, f"[{_region_group_label(region, group_index)}] 查询安全组失败: {message}", ""
        ok, message, sg_data = create_default_security_group(region, vpc_id, group_index=group_index)  # 不存在才创建安全组。 
        if not ok and "InvalidVpc.InvalidStatus" in message:  # 针对 VPC 状态未就绪的错误做重试。 
            for attempt in range(1, 6):  # 最多重试 5 次等待后再创建。 
                _print_region_message(region, f"VPC 尚未就绪，等待后重试安全组创建 ({attempt}/5)", group_index)
                time.sleep(5) 
                ok, message, sg_data = create_default_security_group(region, vpc_id, group_index=group_index) 
                if ok: 
                    break 
        if not ok:  # 判断安全组创建是否失败。 
            return False, message, "" 
        sg_result = sg_data.get("Result") or sg_data.get("result") or sg_data  # 兼容不同响应结构。 
        sg_id = _extract_id(sg_result, "SecurityGroupId", "security_group_id")  # 提取安全组 ID。 
        if sg_id:  # 若返回了安全组 ID 则打印。 
            _print_region_message(region, f"安全组创建成功: {sg_name}", group_index)
        if not sg_id:  # 未返回安全组 ID 无法继续。 
            return False, f"[{_region_group_label(region, group_index)}] 安全组创建成功但资源标识为空", ""
    ok, message, _ = apply_default_security_group_rules(region, sg_id)  # 确保默认规则存在。 
    if not ok:  # 判断规则授权是否失败。 
        return False, message, "" 
    return True, "安全组就绪", sg_id 

def _finish_region_group_network(region: str, group_index: int, instance_id: str, preallocated_eips: List[Dict]) -> int:  # 补齐网卡私网 IP、公网 IP 绑定并输出摘要。 
    def print_ecs_progress(attempt: int, total: int, status: str) -> None:
        status_text = {
            "PENDING": "启动中",
            "STARTING": "启动中",
            "RUNNING": "运行中",
            "STOPPING": "停止中",
            "STOPPED": "已停止",
        }.get(str(status or "").upper(), str(status or "查询中"))
        _print_region_message(region, f"查询ECS状态：{status_text}（{attempt}/{total}）", group_index)

    def print_nic_progress(attempt: int, total: int, found: int, expected: int) -> None:
        _print_region_message(region, f"查询网卡状态：已发现{found}/{expected}张（{attempt}/{total}）", group_index)

    _print_region_step(region, "云服务器启动中（最长180秒）。", group_index)
    ok, message, status = wait_for_instance_running(
        region,
        instance_id,
        timeout=180,
        interval=5,
        progress_callback=print_ecs_progress,
    ) 
    if not ok: 
        print(f"实例未就绪: {message}") 
        return 8 
    _print_region_step(region, "云服务器已运行，网卡准备中（最长120秒）。", group_index)
    expected_nic_count = 2 if data.ECS_SECONDARY_IP else 1 
    ok, message, nic_list = wait_for_instance_network_interfaces(
        region,
        instance_id,
        expected_count=expected_nic_count,
        timeout=120,
        interval=3,
        progress_callback=print_nic_progress,
    ) 
    if not ok: 
        print(f"获取实例网卡失败: {message}") 
        return 8 
    primary_nic_id = "" 
    secondary_nic_id = "" 
    for nic in nic_list: 
        nic_id = nic.get("NetworkInterfaceId") or nic.get("network_interface_id") or "" 
        primary_ip = nic.get("PrimaryIpAddress") or nic.get("primary_ip_address") or "" 
        if primary_ip == data.ECS_PRIMARY_IP: 
            primary_nic_id = nic_id 
        elif data.ECS_SECONDARY_IP and primary_ip == data.ECS_SECONDARY_IP: 
            secondary_nic_id = nic_id 
    if not primary_nic_id and nic_list: 
        primary_nic_id = nic_list[0].get("NetworkInterfaceId") or nic_list[0].get("network_interface_id") or "" 
    if data.ECS_SECONDARY_IP and not secondary_nic_id and len(nic_list) > 1: 
        secondary_nic_id = nic_list[1].get("NetworkInterfaceId") or nic_list[1].get("network_interface_id") or "" 
    if not primary_nic_id: 
        print("未获取到主网卡 ID，无法绑定私网 IP") 
        return 8 
    _print_region_step(region, "网卡已就绪，正在配置私网IP。", group_index)
    ok, message, _ = assign_private_ips(region, primary_nic_id, data.ECS_PRIMARY_EXTRA_IPS) 
    if not ok: 
        print(message) 
        return 8 
    if data.ECS_SECONDARY_IP and secondary_nic_id: 
        ok, message, _ = assign_private_ips(region, secondary_nic_id, data.ECS_SECONDARY_EXTRA_IPS) 
        if not ok: 
            print(message) 
            return 8 
    print("私网 IP 绑定完成") 
    if data.ECS_SECONDARY_IP and not secondary_nic_id: 
        print("未获取到副网卡 ID，无法绑定副网卡公网 IP") 
        return 10 
    binding_targets: List[Dict] = [] 
    if data.ECS_PRIMARY_IP: 
        binding_targets.append({"ip": data.ECS_PRIMARY_IP, "instance_type": "EcsInstance", "instance_id": instance_id}) 
    for ip in data.ECS_PRIMARY_EXTRA_IPS: 
        if ip: 
            binding_targets.append({"ip": ip, "instance_type": "NetworkInterface", "instance_id": primary_nic_id}) 
    if data.ECS_SECONDARY_IP: 
        binding_targets.append({"ip": data.ECS_SECONDARY_IP, "instance_type": "NetworkInterface", "instance_id": secondary_nic_id}) 
        for ip in data.ECS_SECONDARY_EXTRA_IPS: 
            if ip: 
                binding_targets.append({"ip": ip, "instance_type": "NetworkInterface", "instance_id": secondary_nic_id}) 
    binding_targets = [target for target in binding_targets if target.get("ip")] 
    if not binding_targets: 
        print("未配置私网 IP，跳过公网 IP 绑定") 
        _print_region_step(region, "网络资源处理完成。", group_index)
        return 0 
    _print_region_step(region, f"正在绑定公网IP（共{len(binding_targets)}条）。", group_index)
    eip_mapping: Dict[str, str] = {} 
    available_eips: List[Dict] = [] 
    seen_eip_ids = set() 
    expected_eip_group = 2 if int(group_index or 1) >= 2 else 1
    group_binding_conflicts: List[str] = []

    def target_matches_bound(target: Dict, bound_target_id: str, bound_private_ip: str) -> bool: 
        if not bound_target_id: 
            return False 
        if bound_private_ip and target.get("ip") != bound_private_ip: 
            return False 
        target_id = str(target.get("instance_id") or "").strip() 
        allowed_ids = {target_id} 
        if target.get("instance_type") == "EcsInstance": 
            allowed_ids.add(instance_id) 
            if bound_private_ip and primary_nic_id: 
                allowed_ids.add(primary_nic_id) 
        else: 
            allowed_ids.add(instance_id) 
        return bound_target_id in {item for item in allowed_ids if item} 

    def mark_bound_target(target: Dict, eip_address: str, allocation_id: str) -> None: 
        target["_already_bound"] = True 
        target["_bound_allocation_id"] = allocation_id 
        if eip_address: 
            eip_mapping[target["ip"]] = eip_address 
        print(f"公网 IP 已绑定，跳过重复绑定: {target['ip']} -> {eip_address or '已绑定公网IP'}")

    def absorb_eip_records(records: List[Dict]) -> None: 
        for eip in records: 
            allocation_id = _eip_value(eip, "allocation_id", "AllocationId", "EipId", "eip_id", "id") 
            eip_address = _eip_value(eip, "eip_address", "EipAddress", "IpAddress", "ip_address") 
            bound_target_id = _eip_value(
                eip,
                "associated_instance_id",
                "AssociatedInstanceId",
                "AssociatedInstanceID",
                "InstanceId",
                "instance_id",
                "NetworkInterfaceId",
                "network_interface_id",
                "EniId",
                "eni_id",
            ) 
            bound_private_ip = _eip_value(
                eip,
                "private_ip_address",
                "PrivateIpAddress",
                "PrivateIp",
                "private_ip",
                "AssociatedPrivateIpAddress",
                "associated_private_ip_address",
            ) 
            eip_name = _eip_value(eip, "name", "Name", "EipName", "eip_name", "AllocationName", "allocation_name")
            eip_group = _eip_group_index(eip)
            if not allocation_id or allocation_id in seen_eip_ids: 
                continue 
            if bound_target_id: 
                seen_eip_ids.add(allocation_id) 
                for target in binding_targets: 
                    if target.get("_already_bound"): 
                        continue 
                    if target_matches_bound(target, bound_target_id, bound_private_ip): 
                        if eip_group and eip_group != expected_eip_group:
                            group_binding_conflicts.append(
                                f"{eip_name or eip_address or '公网IP'} 已绑定到本组私网IP {target['ip']}，但名称属于第{eip_group}套"
                            )
                            break
                        mark_bound_target(target, eip_address, allocation_id) 
                        break 
                continue 
            if eip_group != expected_eip_group:
                continue
            seen_eip_ids.add(allocation_id)
            available_eips.append(eip) 

    absorb_eip_records(preallocated_eips) 
    if group_binding_conflicts:
        print(f"检测到公网IP跨套绑定，已停止本组处理: {'；'.join(group_binding_conflicts)}")
        return 10
    pending_targets = [target for target in binding_targets if not target.get("_already_bound")] 
    if len(available_eips) < len(pending_targets): 
        print(f"可用公网 IP 不足，重新读取公网 IP 后自动补齐绑定: 可用 {len(available_eips)} 条，待绑定 {len(pending_targets)} 条") 
        refreshed_eips = _load_existing_eips(region) 
        absorb_eip_records(refreshed_eips) 
        if group_binding_conflicts:
            print(f"检测到公网IP跨套绑定，已停止本组处理: {'；'.join(group_binding_conflicts)}")
            return 10
        pending_targets = [target for target in binding_targets if not target.get("_already_bound")] 
    if len(available_eips) < len(pending_targets): 
        print(f"可用公网 IP 不足，已有可复用 {len(available_eips)} 条，待绑定 {len(pending_targets)} 条；不会突破单地区配置上限继续购买") 
        return 9 
    for target, eip in zip(pending_targets, available_eips): 
        ip = target["ip"] 
        instance_type = target["instance_type"] 
        target_id = target["instance_id"] 
        allocation_id = _eip_value(eip, "allocation_id", "AllocationId", "EipId", "eip_id", "id") 
        eip_address = _eip_value(eip, "eip_address", "EipAddress", "IpAddress", "ip_address") 
        ok, message, _ = associate_eip_address(region, allocation_id, target_id, ip, instance_type=instance_type) 
        if not ok: 
            lowered = message.lower() 
            if "invalideni.inuse" in lowered or "already been associated" in lowered or "already associated" in lowered: 
                print(f"公网 IP 绑定目标已存在绑定，重新读取并确认: {ip}") 
                seen_eip_ids.discard(allocation_id) 
                refreshed_eips = _load_existing_eips(region) 
                absorb_eip_records(refreshed_eips) 
                if group_binding_conflicts:
                    print(f"检测到公网IP跨套绑定，已停止本组处理: {'；'.join(group_binding_conflicts)}")
                    return 10
                if target.get("_already_bound"): 
                    continue 
                print(f"公网 IP 目标 {ip} 已被占用，但未确认到本组对应公网IP，已停止以避免跨套绑定")
                return 10
            print(message) 
            return 10 
        if eip_address: 
            eip_mapping[ip] = eip_address 
        target["_already_bound"] = True
        target["_bound_allocation_id"] = allocation_id
        print(f"公网 IP 绑定完成: {ip} -> {eip_address}") 
    final_unbound = [target for target in binding_targets if not target.get("_already_bound") and target.get("ip") not in eip_mapping] 
    if final_unbound: 
        print(f"仍有公网 IP 绑定未确认: {', '.join(target['ip'] for target in final_unbound)}") 
        return 10 
    verified = False
    last_pending: List[str] = []
    for verify_attempt in range(1, 7):
        current_records = _load_existing_eips(region, log_count=False)
        current_by_id = {
            _eip_value(item, "allocation_id", "AllocationId", "EipId", "eip_id", "id"): item
            for item in current_records
        }
        pending: List[str] = []
        for target in binding_targets:
            allocation_id = str(target.get("_bound_allocation_id") or "").strip()
            record = current_by_id.get(allocation_id)
            if not allocation_id or not record:
                pending.append(target["ip"])
                continue
            record_group = _eip_group_index(record)
            if record_group and record_group != expected_eip_group:
                print(
                    f"公网IP绑定复查失败: {target['ip']} 对应资源名称属于第{record_group}套，"
                    f"当前应属于第{expected_eip_group}套"
                )
                return 10
            bound_target_id = _eip_value(
                record,
                "associated_instance_id",
                "AssociatedInstanceId",
                "AssociatedInstanceID",
                "InstanceId",
                "instance_id",
                "NetworkInterfaceId",
                "network_interface_id",
                "EniId",
                "eni_id",
            )
            bound_private_ip = _eip_value(
                record,
                "private_ip_address",
                "PrivateIpAddress",
                "PrivateIp",
                "private_ip",
                "AssociatedPrivateIpAddress",
                "associated_private_ip_address",
            )
            if not target_matches_bound(target, bound_target_id, bound_private_ip):
                pending.append(target["ip"])
                continue
            eip_address = _eip_value(record, "eip_address", "EipAddress", "IpAddress", "ip_address")
            if eip_address:
                eip_mapping[target["ip"]] = eip_address
        if not pending:
            verified = True
            break
        last_pending = pending
        print(f"公网IP绑定复查中: 待确认 {','.join(pending)}（{verify_attempt}/6）")
        if verify_attempt < 6:
            time.sleep(2)
    if not verified:
        print(f"公网IP绑定复查失败，以下私网IP未确认正确归属: {','.join(last_pending)}")
        return 10
    print("公网 IP 申请与绑定完成") 
    summary_tag = _summary_tag(region, group_index) 
    server_label = "One" if group_index == 1 else "Two" 
    public_primary_ip = eip_mapping.get(data.ECS_PRIMARY_IP, "") or "未绑定" 
    print(f"[{summary_tag}] 地区: {_region_label(region)}") 
    print(f"[{summary_tag}] 主网卡IP: {public_primary_ip}") 
    print(f"[{summary_tag}] 服务器密码: {data.ECS_PASSWORD}") 
    print(f"[{summary_tag}] 服务器: {server_label}") 
    _print_region_step(region, "资源处理完成，公网IP结果已返回。", group_index)
    return 0 

def _run_region_group(region: str, group_index: int, preallocated_eips: List[Dict]) -> int:  # 执行单地域单组流程。 
    _, _, _, ecs_name = _resource_names(group_index)  # 获取实例名称。 
    _print_region_step(region, "正在核对现有云服务器。", group_index)
    ok, message, existing_ecs = find_instance_by_name(region, ecs_name)  # 执行前再次确认 ECS 是否已存在。 
    if ok:  # ECS 已存在则跳过 ECS 创建，但继续补齐网卡、公网 IP 绑定。 
        instance_id = _extract_id(existing_ecs, "InstanceId", "instance_id") 
        if not instance_id: 
            _print_region_message(region, f"云服务器 {ecs_name} 已存在，但资源标识为空，无法继续补齐后续资源", group_index)
            return 7 
        _print_region_message(region, f"复用已有云服务器: {ecs_name}，继续补齐后续资源", group_index)
        _print_region_step(region, "已复用云服务器，正在补齐网卡和公网IP。", group_index)
        return _finish_region_group_network(region, group_index, instance_id, preallocated_eips) 
    if message and "未找到" not in message:  # 查询失败且不是不存在。 
        _print_region_message(region, f"查询云服务器失败: {message}", group_index)
        return 7 
    if _new_ip_only_mode():
        _print_region_message(region, f"云服务器 {ecs_name} 不存在，购买选择地区新IP不会创建新云服务器", group_index)
        return 11
    _print_region_step(region, "正在检查VPC和子网。", group_index)
    ok, message, vpc_id = _ensure_vpc(region, group_index)  # 查询或创建 VPC。 
    if not ok:  # 判断 VPC 是否就绪。 
        print(message) 
        return 3 
    ok, message, subnet_id = _ensure_subnet(region, vpc_id, group_index)  # 查询或创建子网。 
    if not ok:  # 判断子网是否就绪。 
        print(message) 
        return 4 
    ok, wait_message = wait_for_vpc_available(region, vpc_id)  # 再次确认 VPC 状态，避免安全组创建报错。 
    if not ok:  # 判断等待是否超时或失败。 
        print(wait_message) 
        return 4 
    existing_ok, existing_message, existing_instance_id = _find_existing_instance_by_network(region, vpc_id, subnet_id) 
    if existing_ok: 
        _print_region_message(region, "按私网IP发现已有云服务器，跳过创建并继续补齐绑定", group_index)
        _print_region_step(region, "已复用云服务器，正在补齐网卡和公网IP。", group_index)
        return _finish_region_group_network(region, group_index, existing_instance_id, preallocated_eips) 
    if existing_message and "未找到" not in existing_message: 
        _print_region_message(region, f"按私网IP查询已有云服务器提示: {existing_message}，继续尝试创建", group_index)
    cleanup_ok, _ = _cleanup_orphan_fixed_ip_nics(region, vpc_id, subnet_id, "创建前固定私网 IP 检查", group_index)
    if not cleanup_ok: 
        return 7 
    _print_region_step(region, "网络资源已就绪，正在配置安全组。", group_index)
    ok, message, sg_id = _ensure_security_group(region, vpc_id, group_index)  # 查询或创建安全组并补齐规则。 
    if not ok:  # 判断安全组是否就绪。 
        print(message) 
        return 5 
    if not subnet_id:  # 判断子网 ID 是否为空，避免实例创建失败。 
        print("子网 ID 为空，无法创建实例")  # 输出缺失提示便于排查。 
        return 7  # 返回非 0 退出码表示实例创建失败。 
    if not sg_id:  # 判断安全组 ID 是否为空，避免实例创建失败。 
        print("安全组 ID 为空，无法创建实例")  # 输出缺失提示便于排查。 
        return 7  # 返回非 0 退出码表示实例创建失败。 
    _print_region_step(region, "正在创建云服务器。", group_index)
    ok, message, ecs_data = create_default_instance(region, subnet_id, sg_id, group_index=group_index)  # 使用默认配置创建 ECS 实例。 
    if not ok:  # 判断实例创建是否失败。 
        if not _is_private_ip_conflict(message): 
            _print_region_message(region, _friendly_ecs_create_failure(message), group_index)
            return 7  # 返回非 0 退出码表示实例创建失败。 
        existing_ok, existing_message, existing_instance_id = _find_existing_instance_by_network(region, vpc_id, subnet_id) 
        if existing_ok: 
            _print_region_message(region, "云服务器创建返回私网IP占用，已反查到旧云服务器，继续补齐绑定", group_index)
            _print_region_step(region, "已找到可复用云服务器，正在补齐网卡和公网IP。", group_index)
            return _finish_region_group_network(region, group_index, existing_instance_id, preallocated_eips) 
        _print_region_message(region, f"云服务器创建私网IP冲突，但未能反查到旧云服务器: {existing_message}", group_index)
        _print_region_step(region, "固定私网IP被残留网卡占用，正在清理后再次创建。", group_index)
        cleanup_ok, _ = _cleanup_orphan_fixed_ip_nics(region, vpc_id, subnet_id, "创建失败后固定私网 IP 检查", group_index)
        if cleanup_ok: 
            time.sleep(3) 
            ok, message, ecs_data = create_default_instance(region, subnet_id, sg_id, group_index=group_index) 
            if ok: 
                _print_region_message(region, "固定私网 IP 占用清理后，云服务器创建重试成功", group_index)
            else: 
                existing_ok, existing_message, existing_instance_id = _find_existing_instance_by_network(region, vpc_id, subnet_id) 
                if existing_ok: 
                    _print_region_message(region, "云服务器重试仍返回占用，但已反查到旧云服务器，继续补齐绑定", group_index)
                    _print_region_step(region, "已找到可复用云服务器，正在补齐网卡和公网IP。", group_index)
                    return _finish_region_group_network(region, group_index, existing_instance_id, preallocated_eips) 
                _print_region_message(region, f"固定私网 IP 占用清理后重试仍失败: {existing_message}", group_index)
        if not ok: 
            _print_region_message(region, _friendly_ecs_create_failure(message), group_index)
            return 7 
    ecs_result = ecs_data.get("Result") or ecs_data.get("result") or ecs_data  # 兼容不同响应结构。 
    instance_ids = ecs_result.get("InstanceIds") or ecs_result.get("instance_ids") or []  # 提取实例 ID 列表。 
    if instance_ids:  # 若存在实例 ID 则打印。 
        _print_region_message(region, f"云服务器创建成功: {ecs_name}", group_index)
    else:  # 若未返回实例 ID 则给出提示。 
        print("实例创建成功，但未返回实例 ID")  # 输出提示便于确认。 
        return 7  # 无实例 ID 无法继续绑定私网 IP。 
    instance_id = instance_ids[0]  # 取首个实例 ID 用于后续网卡操作。 
    return _finish_region_group_network(region, group_index, instance_id, preallocated_eips) 
    ok, message, status = wait_for_instance_running(  # 等待实例进入运行状态。 
        region,  # 传入地域。 
        instance_id,  # 传入实例 ID。 
        timeout=180,  # 增加等待时间避免实例启动延迟。 
        interval=5,  # 轮询间隔。 
    )  # 等待结束。 
    if not ok:  # 判断实例状态等待是否失败。 
        print(f"实例未就绪: {message}")  # 输出失败原因便于排查。 
        return 8  # 返回非 0 退出码表示实例未就绪。 
    expected_nic_count = 2 if data.ECS_SECONDARY_IP else 1  # 根据配置决定期望网卡数量。 
    ok, message, nic_list = wait_for_instance_network_interfaces(  # 等待实例网卡信息就绪。 
        region,  # 传入地域。 
        instance_id,  # 传入实例 ID。 
        expected_count=expected_nic_count,  # 期望网卡数量。 
        timeout=120,  # 增加等待时间避免云端延迟。 
        interval=3,  # 轮询间隔。 
    )  # 等待结束。 
    if not ok:  # 判断网卡查询是否失败。 
        print(f"获取实例网卡失败: {message}")  # 输出失败原因便于排查。 
        return 8  # 返回非 0 退出码表示网卡查询失败。 
    primary_nic_id = ""  # 主网卡 ID。 
    secondary_nic_id = ""  # 副网卡 ID。 
    for nic in nic_list:  # 遍历网卡列表匹配主/副网卡。 
        nic_id = nic.get("NetworkInterfaceId") or nic.get("network_interface_id") or ""  # 读取网卡 ID。 
        primary_ip = nic.get("PrimaryIpAddress") or nic.get("primary_ip_address") or ""  # 读取主 IP。 
        if primary_ip == data.ECS_PRIMARY_IP:  # 匹配主网卡 IP。 
            primary_nic_id = nic_id  # 保存主网卡 ID。 
        elif data.ECS_SECONDARY_IP and primary_ip == data.ECS_SECONDARY_IP:  # 匹配副网卡 IP。 
            secondary_nic_id = nic_id  # 保存副网卡 ID。 
    if not primary_nic_id and nic_list:  # 若未匹配到主网卡则兜底取第一张。 
        primary_nic_id = nic_list[0].get("NetworkInterfaceId") or nic_list[0].get("network_interface_id") or "" 
    if data.ECS_SECONDARY_IP and not secondary_nic_id and len(nic_list) > 1:  # 副网卡兜底取第二张。 
        secondary_nic_id = nic_list[1].get("NetworkInterfaceId") or nic_list[1].get("network_interface_id") or "" 
    if not primary_nic_id:  # 兜底后仍为空则返回失败。 
        print("未获取到主网卡 ID，无法绑定私网 IP")  # 输出缺失提示。 
        return 8  # 返回非 0 退出码表示网卡信息缺失。 
    ok, message, _ = assign_private_ips(region, primary_nic_id, data.ECS_PRIMARY_EXTRA_IPS)  # 绑定主网卡私网 IP。 
    if not ok:  # 判断绑定是否失败。 
        print(message)  # 输出失败原因便于排查。 
        return 8  # 返回非 0 退出码表示私网 IP 绑定失败。 
    if data.ECS_SECONDARY_IP and secondary_nic_id:  # 若配置了副网卡且找到 ID 则绑定副网卡 IP。 
        ok, message, _ = assign_private_ips(region, secondary_nic_id, data.ECS_SECONDARY_EXTRA_IPS)  # 绑定副网卡私网 IP。 
        if not ok:  # 判断绑定是否失败。 
            print(message)  # 输出失败原因便于排查。 
            return 8  # 返回非 0 退出码表示私网 IP 绑定失败。 
    print("私网 IP 绑定完成")  # 输出完成提示。 
    if data.ECS_SECONDARY_IP and not secondary_nic_id:  # 有副网卡配置但未拿到 ID 时直接返回失败。 
        print("未获取到副网卡 ID，无法绑定副网卡公网 IP")  # 输出缺失提示。 
        return 10  # 返回非 0 退出码表示公网 IP 绑定失败。 
    binding_targets = []  # 构建公网 IP 绑定目标列表。 
    if data.ECS_PRIMARY_IP:  # 主网卡主 IP 绑定到 ECS 实例。 
        binding_targets.append({  # 记录主网卡主 IP 绑定目标。 
            "ip": data.ECS_PRIMARY_IP,  # 主网卡主 IP。 
            "instance_type": "EcsInstance",  # 绑定类型为 ECS。 
            "instance_id": instance_id,  # 绑定目标为实例 ID。 
        }) 
    for ip in data.ECS_PRIMARY_EXTRA_IPS:  # 追加主网卡附加 IP 到主网卡。 
        if ip:  # 跳过空值。 
            binding_targets.append({ 
                "ip": ip,  # 私网 IP。 
                "instance_type": "NetworkInterface",  # 绑定类型为网卡。 
                "instance_id": primary_nic_id,  # 绑定到主网卡 ID。 
            }) 
    if data.ECS_SECONDARY_IP:  # 副网卡主 IP 绑定到副网卡。 
        binding_targets.append({ 
            "ip": data.ECS_SECONDARY_IP,  # 副网卡主 IP。 
            "instance_type": "NetworkInterface",  # 绑定类型为网卡。 
            "instance_id": secondary_nic_id,  # 绑定到副网卡 ID。 
        }) 
        for ip in data.ECS_SECONDARY_EXTRA_IPS:  # 追加副网卡附加 IP。 
            if ip: 
                binding_targets.append({ 
                    "ip": ip,  # 私网 IP。 
                    "instance_type": "NetworkInterface",  # 绑定类型为网卡。 
                    "instance_id": secondary_nic_id,  # 绑定到副网卡 ID。 
                }) 
    binding_targets = [target for target in binding_targets if target.get("ip")]  # 过滤空值避免请求报错。 
    if not binding_targets:  # 若没有可用私网 IP 则无需绑定公网 IP。 
        print("未配置私网 IP，跳过公网 IP 绑定")  # 输出提示。 
        return 0  # 返回成功退出码。 
    if len(preallocated_eips) < len(binding_targets):  # 申请数量不足时直接终止。 
        print("公网 IP 申请数量不足，无法完成绑定")  # 输出不足提示。 
        return 9  # 返回非 0 退出码表示公网 IP 申请失败。 
    eip_mapping: Dict[str, str] = {}  # 保存私网 IP -> 公网 IP 的映射，便于摘要输出。 
    for target, eip in zip(binding_targets, preallocated_eips):  # 逐个绑定公网 IP 到私网 IP。 
        ip = target["ip"]  # 读取当前私网 IP。 
        instance_type = target["instance_type"]  # 读取绑定类型。 
        target_id = target["instance_id"]  # 读取绑定目标 ID。 
        allocation_id = eip.get("allocation_id", "")  # 读取公网 IP ID。 
        eip_address = eip.get("eip_address", "")  # 读取公网 IP 地址。 
        ok, message, _ = associate_eip_address(  # 绑定公网 IP。 
            region,  # 传入地域。 
            allocation_id,  # 公网 IP ID。 
            target_id,  # 绑定目标实例/网卡 ID。 
            ip,  # 绑定目标私网 IP。 
            instance_type=instance_type,  # 绑定类型。 
        )  # 绑定结束。 
        if not ok:  # 判断绑定是否失败。 
            print(message)  # 输出失败原因便于排查。 
            return 10  # 返回非 0 退出码表示公网 IP 绑定失败。 
        if eip_address:  # 仅在公网 IP 有值时记录映射。 
            eip_mapping[ip] = eip_address  # 记录私网 IP 对应的公网 IP。 
        print(f"公网 IP 绑定完成: {ip} -> {eip_address}")  # 输出绑定结果便于确认。 
    print("公网 IP 申请与绑定完成")  # 输出全部公网 IP 绑定完成提示。 
    summary_tag = _summary_tag(region, group_index)  # 构建地区输出卡片标签。 
    server_label = "One" if group_index == 1 else "Two"  # 按分组输出 One/Two。 
    public_primary_ip = eip_mapping.get(data.ECS_PRIMARY_IP, "")  # 读取主网卡公网 IP。 
    if not public_primary_ip:  # 若未获取到公网 IP 则给出提示文本。 
        public_primary_ip = "未绑定"  # 显示未绑定，避免误把内网 IP 当公网。 
    print(f"[{summary_tag}] 地区: {_region_label(region)}")  # 输出地区。 
    print(f"[{summary_tag}] 主网卡IP: {public_primary_ip}")  # 输出主网卡公网 IP。 
    print(f"[{summary_tag}] 服务器密码: {data.ECS_PASSWORD}")  # 输出服务器密码。
    print(f"[{summary_tag}] 服务器: {server_label}")  # 输出服务器分组标识。
    return 0  # 余额满足且流程完整执行时返回 0。

def _main_purchase_flow() -> int:  # 先统一买齐公网 IP，再进入云服务器创建/补齐流程。
    region_counts = _parse_region_counts(None)  # 根据环境变量与默认配置解析地域数量。
    if not region_counts:
        print("未找到可执行的地域配置")
        return 1
    new_ip_only = _new_ip_only_mode()
    used_ip_filter = _used_ip_filter() if new_ip_only else set()
    requested_groups = _parse_region_groups()
    if new_ip_only and not used_ip_filter:
        print("旧IP库为空，无法过滤已使用IP，已停止购买选择地区新IP")
        return 9

    tasks: List[Tuple[str, int]] = []  # 保存需要执行的地域与分组。
    for region_value, count in region_counts.items():
        _print_region_step(region_value, "正在检查已有云服务器和公网IP。")
        planned_groups = _plan_groups_for_region(region_value, count)
        if region_value in requested_groups:
            planned_groups = [
                group_index
                for group_index in planned_groups
                if group_index in requested_groups[region_value]
            ]
        tasks.extend((region_value, group_index) for group_index in planned_groups)
        if planned_groups:
            _print_region_step(region_value, f"复用检查完成，待处理{len(planned_groups)}台云服务器。")
        else:
            _print_region_step(region_value, "已有资源满足需求，本地区无需新购。")
    if not tasks:
        if new_ip_only:
            print("所选地区没有可复用的云服务器，购买选择地区新IP不会创建新云服务器")
            return 11
        print("未发现需要创建的资源，流程结束")
        return 0

    for region_value, group_index in tasks:
        _print_region_step(region_value, "正在检查残留网卡和网络资源。", group_index)
        if not _cleanup_orphan_fixed_ip_nics_before_eip_plan(region_value, group_index):
            return 7

    ip_order = _build_ip_order()
    group_ip_count = len(ip_order)
    task_groups_by_region: Dict[str, List[int]] = {region_value: [] for region_value in region_counts}
    for region_value, group_index in tasks:
        task_groups_by_region.setdefault(region_value, []).append(group_index)
    region_target_limits: Dict[str, int] = {}
    for region_value, count in region_counts.items():
        planned_max_group = max(task_groups_by_region.get(region_value) or [1])
        target_group_count = 2 if count >= 2 or planned_max_group >= 2 or _group_exists(region_value, 2) else 1
        region_target_limits[region_value] = min(16, target_group_count * group_ip_count)
    total_target_ips = sum(region_target_limits.values())
    region_existing_eips: Dict[str, List[Dict]] = {}
    for region_value in region_counts:
        _print_region_step(region_value, "正在读取可复用公网IP。")
        region_existing_eips[region_value] = _load_existing_eips(region_value)
        if new_ip_only:
            reusable_eips: List[Dict] = []
            for eip in region_existing_eips[region_value]:
                eip_address = _eip_value(eip, "eip_address", "EipAddress", "IpAddress", "ip_address")
                allocation_id = _eip_value(eip, "allocation_id", "AllocationId", "EipId", "eip_id", "id")
                if _eip_is_bound(eip) or not eip_address or eip_address not in used_ip_filter:
                    reusable_eips.append(eip)
                    continue
                ok, message, _ = release_eip_address(region_value, allocation_id)
                if not ok:
                    _print_region_message(region_value, f"未绑定旧公网IP {eip_address} 释放失败，已停止继续购买: {message}")
                    return 9
                _print_region_message(region_value, f"未绑定公网IP {eip_address} 已命中旧IP库，释放后购买新IP")
            region_existing_eips[region_value] = reusable_eips
    region_used_unbound_ids = {region_value: set() for region_value in region_counts}
    region_planned_eip_counts = {region_value: len(region_existing_eips.get(region_value, [])) for region_value in region_counts}
    task_preallocated_eips: Dict[Tuple[str, int], List[Dict]] = {}
    task_buy_counts: Dict[Tuple[str, int], int] = {}
    task_group_exists: Dict[Tuple[str, int], bool] = {}
    region_reserved_eip_capacity = {region_value: 0 for region_value in region_counts}
    total_new_eips = 0

    if ip_order:
        for region_value, group_index in tasks:
            task_key = (region_value, group_index)
            group_exists = _group_exists(region_value, group_index)
            task_group_exists[task_key] = group_exists
            if group_exists:
                region_reserved_eip_capacity[region_value] = region_reserved_eip_capacity.get(region_value, 0) + group_ip_count
        for region_value, group_index in tasks:
            task_key = (region_value, group_index)
            group_exists = task_group_exists.get(task_key, False)
            existing_eips = region_existing_eips.get(region_value, [])
            region_limit = region_target_limits.get(region_value, group_ip_count)
            current_region_count = region_planned_eip_counts.get(region_value, 0)
            if group_exists:
                # 已存在的 ECS 先按分组容量预留已有 EIP，避免 48 条补第二组时把第一组已绑定 IP 当成新组缺口。
                group_floor = (group_index - 1) * group_ip_count
                available_existing = min(group_ip_count, max(0, current_region_count - group_floor))
                preallocated_eips: List[Dict] = []
            else:
                selectable_existing = min(
                    group_ip_count,
                    max(0, current_region_count - region_reserved_eip_capacity.get(region_value, 0)),
                )
                preallocated_eips = _select_existing_eips_for_group(
                    existing_eips,
                    region_used_unbound_ids[region_value],
                    selectable_existing,
                    group_index,
                )
                available_existing = sum(1 for item in preallocated_eips if not _eip_is_bound(item))
            need_new = max(0, group_ip_count - available_existing)
            allowed_new = max(0, region_limit - current_region_count)
            buy_count = min(need_new, allowed_new)
            task_preallocated_eips[task_key] = list(preallocated_eips)
            task_buy_counts[task_key] = buy_count
            total_new_eips += buy_count
            region_planned_eip_counts[region_value] = current_region_count + buy_count
            # 新建 ECS 前必须确保本组公网 IP 数量可补齐；若地区配额已满且本组 ECS 不存在，提前停止，避免先建 ECS 后缺 IP。
            if need_new > buy_count and not group_exists:
                print(
                    f"[{region_value}] 公网 IP 未能买齐: 已复用 {available_existing}/{group_ip_count}，"
                    f"计划新购 {buy_count}，地区目标上限 {region_limit}，不会创建云服务器"
                )
                return 9
    else:
        print("未配置私网 IP，跳过公网 IP 预购阶段")

    if total_new_eips > 0:
        print("步骤: 正在校验购买公网IP所需余额。", flush=True)
        ok, message, balance = get_available_balance("", "", "")
        if not ok:
            print(f"余额查询失败: {message}")
            return 1
        if balance is None:
            print("余额为空，无法继续流程")
            return 1
        required_balance = _balance_threshold_for_ip_count(total_target_ips)
        print(f"余额查询成功，可用余额: {balance}")
        print(f"本次目标公网 IP 数量 {total_target_ips}，预计新购 {total_new_eips} 条，官方余额门槛 {required_balance} 元")
        if balance < required_balance:
            print(
                f"账户可用余额不足 {required_balance} 元，官方规定购买公网 IP 资源需余额≥{required_balance}元；"
                f"已停止任务，请先充值至 {required_balance} 元以上后重试"
            )
            return 2
        print(f"余额满足购买公网 IP 资源需≥{required_balance}元的官方门槛，开始预购公网 IP")
    else:
        print("已有公网 IP 满足本次需求，跳过余额门槛检查")

    for region_value, group_index in tasks:
        _, _, _, ecs_name = _resource_names(group_index)
        print(
            f"[{_region_group_label(region_value, group_index)}] "
            f"开始执行地域 {region_value} 第 {group_index} 组资源({ecs_name})"
        )
        task_key = (region_value, group_index)
        preallocated_eips = list(task_preallocated_eips.get(task_key, []))
        buy_count = task_buy_counts.get(task_key, 0)
        region_limit = region_target_limits.get(region_value, group_ip_count)
        if ip_order and buy_count > 0:
            _print_region_step(region_value, f"正在申请公网IP（{buy_count}条）。", group_index)
            name_prefix = data.EIP_NAME_PREFIX_GROUP_2 if group_index == 2 else data.EIP_NAME_PREFIX_GROUP_1
            ok, message, new_eips = allocate_eip_batch(
                region_value,
                buy_count,
                bandwidth=data.EIP_BANDWIDTH,
                billing_type=data.EIP_BILLING_TYPE,
                isp=data.EIP_ISP,
                project_name=data.EIP_PROJECT_NAME,
                name_prefix=name_prefix,
                excluded_ips=sorted(used_ip_filter) if new_ip_only else None,
                duplicate_hit_limit=3,
            )
            if not ok:
                print(message)
                return 9
            new_eips = [_normalize_eip_record(item) for item in new_eips]
            preallocated_eips.extend(new_eips)
            region_existing_eips[region_value].extend(new_eips)
            for item in new_eips:
                allocation_id = _eip_value(item, "allocation_id", "AllocationId", "EipId", "eip_id", "id")
                if allocation_id:
                    region_used_unbound_ids[region_value].add(allocation_id)
            task_preallocated_eips[task_key] = preallocated_eips
            _print_region_message(region_value, f"本组预购公网 IP 数量: {len(new_eips)}，当前地域目标不超过 {region_limit}", group_index)
        elif ip_order:
            if len(preallocated_eips) >= group_ip_count:
                _print_region_message(region_value, "已有公网 IP 可复用，跳过购买公网 IP", group_index)
            else:
                _print_region_message(region_value, "已有公网 IP 达到配置数量，跳过购买公网 IP", group_index)
        if ip_order:
            _print_region_step(region_value, "公网IP准备完成。", group_index)

    print("本次公网 IP 预购/复用检查完成，开始创建/补齐云服务器")
    for region_value, group_index in tasks:
        task_key = (region_value, group_index)
        _print_region_step(region_value, "开始处理云服务器和网络资源。", group_index)
        exit_code = _run_region_group(region_value, group_index, task_preallocated_eips.get(task_key, []))
        if exit_code != 0:
            return exit_code
    return 0

def main() -> int:  # 定义主入口函数并返回退出码。
    return _main_purchase_flow()

if __name__ == "__main__":  # 判断是否以脚本方式执行。
    sys.exit(main())  # 执行主函数并将结果作为进程退出码。 
