import time  # 导入时间模块用于轮询主网卡端口信息。 
from huaweicloudsdkcore.auth.credentials import BasicCredentials  # 导入华为云 SDK 的基础鉴权对象。 
from huaweicloudsdkcore.exceptions.exceptions import ClientRequestException  # 导入 SDK 请求异常用于重试判断。 
from huaweicloudsdkcore.region.region import Region  # 导入区域对象用于设置服务端点。 
from huaweicloudsdkvpc.v3 import VpcClient  # 导入 VPC v3 客户端用于辅助网卡接口。 
from huaweicloudsdkvpc.v3 import CreateSubNetworkInterfaceRequest, ListSubNetworkInterfacesRequest  # 导入创建与查询辅助网卡请求模型。 
from huaweicloudsdkecs.v2 import EcsClient, ShowServerRequest, ListServerInterfacesRequest  # 导入 ECS 客户端与查询服务器请求模型。 
from config import PROJECT_ID, get_runtime_ak_sk  # 导入鉴权参数用于构造客户端。 
from iam_service import get_project_id  # 导入自动获取项目 ID 的函数。 
from data import VPC_ENDPOINT_TEMPLATE, ECS_ENDPOINT_TEMPLATE  # 导入 VPC 与 ECS 服务端点模板。 
from data import AUX_NIC_DRY_RUN, AUX_NIC_DESCRIPTION, AUX_NIC_IPV6_ENABLE  # 导入辅助网卡创建配置。 
from data import AUX_NIC_ALLOWED_ADDRESS_PAIRS, AUX_NIC_WAIT_TIMEOUT, AUX_NIC_POLL_INTERVAL  # 导入辅助网卡轮询配置。
from data import LOG_ENABLED  # 导入日志开关用于输出进度。
from retry_utils import retry_cloud_call  # 导入华为云临时网络异常重试工具。
def _resolve_project_id(region_id):  # 根据配置或 IAM 自动解析项目 ID。 
    if PROJECT_ID and PROJECT_ID != "YOUR_PROJECT_ID":  # 若已手工配置项目 ID 则直接使用。 
        return PROJECT_ID  # 返回手工配置的项目 ID。 
    return get_project_id(region_id)  # 否则调用 IAM 接口自动获取项目 ID。 
def _build_vpc_v3_client(region_id):  # 创建 VPC v3 客户端的函数。 
    project_id = _resolve_project_id(region_id)  # 获取当前区域对应的项目 ID。 
    ak, sk = get_runtime_ak_sk()  # 读取当前线程生效的 AK/SK。 
    creds = BasicCredentials(ak, sk).with_project_id(project_id)  # 使用 AK/SK 与项目 ID 构造鉴权对象。 
    endpoint = VPC_ENDPOINT_TEMPLATE.format(region_id=region_id)  # 根据区域拼接 VPC 服务端点。 
    region = Region(region_id, endpoint)  # 创建区域对象并绑定端点。 
    client = VpcClient.new_builder().with_credentials(creds).with_region(region).build()  # 构建 VPC v3 客户端实例。 
    return client  # 返回构建好的客户端对象。 
def _build_ecs_client(region_id):  # 创建 ECS 客户端的函数。 
    project_id = _resolve_project_id(region_id)  # 获取当前区域对应的项目 ID。 
    ak, sk = get_runtime_ak_sk()  # 读取当前线程生效的 AK/SK。 
    creds = BasicCredentials(ak, sk).with_project_id(project_id)  # 使用 AK/SK 与项目 ID 构造鉴权对象。 
    endpoint = ECS_ENDPOINT_TEMPLATE.format(region_id=region_id)  # 根据区域拼接 ECS 服务端点。 
    region = Region(region_id, endpoint)  # 创建区域对象并绑定端点。 
    client = EcsClient.new_builder().with_credentials(creds).with_region(region).build()  # 构建 ECS 客户端实例。 
    return client  # 返回构建好的客户端对象。 
def _get_primary_port_id(region_id, server_id, main_nic_ip):  # 获取服务器主网卡端口 ID 的函数。 
    client = _build_ecs_client(region_id)  # 构建 ECS 客户端用于查询服务器详情。 
    start_time = time.time()  # 记录轮询开始时间。 
    while True:  # 循环查询直到超时或获取到主网卡。 
        if LOG_ENABLED:  # 若开启日志输出则提示当前查询状态。 
            print("步骤: 等待主网卡端口")  # 输出等待主网卡端口日志。 
        try:  # 捕获查询实例详情时的临时错误。 
            response = retry_cloud_call("查询ECS网卡", lambda: client.show_server(ShowServerRequest(server_id=server_id)))  # 查询云服务器详情。
        except ClientRequestException as exc:  # 捕获请求异常用于重试处理。 
            if "Ecs.0114" in str(exc) or "404" in str(exc):  # 实例未就绪或未找到时继续等待。 
                if time.time() - start_time > AUX_NIC_WAIT_TIMEOUT:  # 超过超时时间仍未获取到实例。 
                    raise TimeoutError("等待云服务器就绪超时")  # 抛出超时异常提示。 
                time.sleep(AUX_NIC_POLL_INTERVAL)  # 等待指定间隔后继续轮询。 
                continue  # 跳过本次循环进入下一轮查询。 
            raise  # 非预期错误则直接抛出。 
        interfaces = response.server.network_interfaces or []  # 获取网卡列表并处理空值。 
        for interface in interfaces:  # 优先查找标记为 primary 的网卡。 
            if interface.primary:  # 如果该网卡是主网卡。 
                return interface.port_id  # 返回主网卡的端口 ID。 
            if main_nic_ip and main_nic_ip in (interface.ip_addresses or []):  # 若匹配主网卡 IP 则返回。 
                return interface.port_id  # 返回匹配到的端口 ID。 
        if interfaces:  # 如果没有标记 primary，但列表非空。 
            return interfaces[0].port_id  # 返回第一个网卡端口 ID 作为兜底。 
        try:  # 如果 show_server 未返回网卡则尝试查询接口列表。 
            iface_resp = retry_cloud_call("查询ECS接口", lambda: client.list_server_interfaces(ListServerInterfacesRequest(server_id=server_id)))  # 查询云服务器接口列表。
        except ClientRequestException:  # 若接口查询失败则继续等待。 
            iface_resp = None  # 失败时置空以进入等待逻辑。 
        if iface_resp:  # 若获取到接口列表则尝试按 IP 匹配。 
            for attachment in iface_resp.interface_attachments or []:  # 遍历接口列表。 
                for fixed_ip in attachment.fixed_ips or []:  # 遍历接口 IP 列表。 
                    if main_nic_ip and fixed_ip.ip_address == main_nic_ip:  # 若匹配主网卡 IP。 
                        return attachment.port_id  # 返回匹配到的端口 ID。 
        if time.time() - start_time > AUX_NIC_WAIT_TIMEOUT:  # 超过超时时间仍未获取到网卡。 
            raise TimeoutError("获取主网卡端口 ID 超时")  # 抛出超时异常提示。 
        time.sleep(AUX_NIC_POLL_INTERVAL)  # 等待指定间隔后继续轮询。 
def _build_aux_ip_list(spec):  # 根据规格生成辅助网卡 IP 列表。 
    main_ip = spec.get("main_nic_ip")  # 获取主网卡 IP。 
    if not main_ip:  # 若主网卡 IP 为空则无法生成网段前缀。 
        raise ValueError("规格中缺少 main_nic_ip，无法生成辅助网卡 IP")  # 抛出异常提示。 
    ip_prefix = ".".join(main_ip.split(".")[:-1]) + "."  # 计算 IP 前缀，例如 192.168.0. 
    start = int(spec.get("aux_nic_ip_start", 0) or 0)  # 获取辅助网卡起始 IP 尾段。 
    end = int(spec.get("aux_nic_ip_end", 0) or 0)  # 获取辅助网卡结束 IP 尾段。 
    count = int(spec.get("aux_nic_count", 0) or 0)  # 获取辅助网卡数量。 
    if start <= 0 or end <= 0 or start > end:  # 校验 IP 范围是否有效。 
        raise ValueError("辅助网卡 IP 范围配置无效，请检查 aux_nic_ip_start/aux_nic_ip_end")  # 抛出异常提示。 
    ip_list = []  # 初始化辅助网卡 IP 列表。 
    secondary_ip = spec.get("secondary_nic_ip")  # 获取副网卡 IP 用于冲突过滤。 
    for suffix in range(start, end + 1):  # 遍历尾段范围生成 IP 列表。 
        candidate = f"{ip_prefix}{suffix}"  # 组合完整 IP 地址。 
        if candidate == main_ip:  # 跳过主网卡 IP。 
            continue  # 继续下一个候选。 
        if secondary_ip and candidate == secondary_ip:  # 若与副网卡 IP 冲突则跳过。 
            continue  # 继续下一个候选。 
        ip_list.append(candidate)  # 将候选 IP 加入列表。 
        if len(ip_list) >= count:  # 若已满足数量要求则停止。 
            break  # 退出循环避免多余 IP。 
    if len(ip_list) < count:  # 若可用 IP 不足则报错。 
        raise RuntimeError("辅助网卡 IP 数量不足，请调整 IP 范围或数量")  # 抛出异常提示。 
    return ip_list  # 返回辅助网卡 IP 列表。 
def _build_sub_network_interface_body(subnet_id, parent_port_id, security_group_id, private_ip):  # 组装辅助网卡请求体的函数。 
    sub_nic_body = {  # 创建辅助网卡请求体字典。 
        "virsubnet_id": subnet_id,  # 辅助网卡所属子网 ID 字段。 
        "parent_id": parent_port_id,  # 辅助网卡宿主弹性网卡端口 ID 字段。 
        "security_groups": [security_group_id] if security_group_id else [],  # 关联安全组列表字段。 
        "description": AUX_NIC_DESCRIPTION,  # 辅助网卡描述字段。 
        "ipv6_enable": AUX_NIC_IPV6_ENABLE,  # 是否开启 IPv6 字段。 
        "allowed_address_pairs": AUX_NIC_ALLOWED_ADDRESS_PAIRS,  # 允许地址对字段。 
        "private_ip_address": private_ip,  # 指定辅助网卡私有 IP 地址。 
    }  # 辅助网卡请求体字典构建结束。 
    filtered_body = {k: v for k, v in sub_nic_body.items() if v not in (None, "", [], {})}  # 过滤空值字段避免接口报错。 
    return filtered_body  # 返回过滤后的请求体字典。 
def _list_existing_aux_nics(region_id, parent_port_id):  # 查询已存在辅助网卡的函数。 
    client = _build_vpc_v3_client(region_id)  # 构建 VPC v3 客户端用于查询。 
    request = ListSubNetworkInterfacesRequest(parent_id=[parent_port_id])  # 按宿主端口 ID 过滤辅助网卡列表。 
    response = retry_cloud_call("查询辅助网卡", lambda: client.list_sub_network_interfaces(request))  # 调用 SDK 查询辅助网卡列表。
    mapping = {}  # 初始化 IP 到辅助网卡 ID 的映射。 
    for item in response.sub_network_interfaces or []:  # 遍历辅助网卡列表。 
        if item.private_ip_address:  # 若存在私网 IP 则记录映射。 
            mapping[item.private_ip_address] = item.id  # 保存 IP 与辅助网卡 ID 的对应关系。 
    return mapping  # 返回 IP 到 ID 的映射结果。 
def create_aux_nic(region_id, server_id, subnet_id, security_group_id, spec):  # 创建辅助网卡的函数实现。 
    count = int(spec.get("aux_nic_count", 0) or 0)  # 获取辅助网卡数量，空值默认为 0。 
    if count <= 0:  # 若无需创建辅助网卡则直接返回空列表。 
        return []  # 返回空列表表示未创建。 
    if not server_id:  # 若服务器 ID 为空则无法继续创建辅助网卡。 
        raise ValueError("server_id 为空，无法创建辅助网卡")  # 抛出异常提示上游处理。 
    if LOG_ENABLED:  # 若开启日志输出则提示开始创建辅助网卡。 
        print(f"步骤: 辅助网卡数量={count}")  # 输出辅助网卡数量信息。 
    parent_port_id = _get_primary_port_id(region_id, server_id, spec.get("main_nic_ip"))  # 获取主网卡端口 ID 作为宿主。 
    ip_list = _build_aux_ip_list(spec)  # 生成辅助网卡 IP 列表。 
    client = _build_vpc_v3_client(region_id)  # 构建 VPC v3 客户端以调用创建接口。 
    created_ids = []  # 初始化已创建辅助网卡 ID 列表。 
    existing_map = _list_existing_aux_nics(region_id, parent_port_id)  # 查询已存在辅助网卡映射。 
    for ip_address in ip_list:  # 逐个 IP 创建或复用辅助网卡。 
        if ip_address in existing_map:  # 若已存在对应 IP 的辅助网卡则复用。 
            created_ids.append(existing_map[ip_address])  # 记录已存在的辅助网卡 ID。 
            continue  # 进入下一条 IP。 
        if LOG_ENABLED:  # 若开启日志输出则提示正在创建的辅助网卡 IP。 
            print(f"步骤: 创建辅助网卡 {ip_address}")  # 输出辅助网卡创建 IP 日志。 
        sub_nic_body = _build_sub_network_interface_body(subnet_id, parent_port_id, security_group_id, ip_address)  # 构建辅助网卡请求体。 
        request = CreateSubNetworkInterfaceRequest()  # 创建辅助网卡请求对象。 
        request.body = {"sub_network_interface": sub_nic_body, "dry_run": AUX_NIC_DRY_RUN}  # 填充请求体与预检开关。 
        try:  # 捕获创建辅助网卡时的可恢复错误。 
            response = retry_cloud_call("创建辅助网卡", lambda: client.create_sub_network_interface(request))  # 调用 SDK 创建辅助网卡。
            created_ids.append(response.sub_network_interface.id)  # 记录创建的辅助网卡 ID。 
        except ClientRequestException as exc:  # 捕获请求异常用于限额处理。 
            if "VPC.9905" in str(exc):  # 若提示配额不足则尝试复用已有网卡。 
                existing_map = _list_existing_aux_nics(region_id, parent_port_id)  # 重新查询辅助网卡映射。 
                if ip_address in existing_map:  # 若在重新查询中找到则复用。 
                    created_ids.append(existing_map[ip_address])  # 记录已存在的辅助网卡 ID。 
                    continue  # 进入下一条 IP。 
            raise  # 其他错误直接抛出。 
    return created_ids  # 返回辅助网卡 ID 列表。 
def get_aux_nic_details(region_id, server_id, spec):  # 获取辅助网卡详情列表的函数。 
    if not server_id:  # 若服务器 ID 为空则无法查询。 
        return []  # 返回空列表表示未查询到结果。 
    parent_port_id = _get_primary_port_id(region_id, server_id, spec.get("main_nic_ip"))  # 获取主网卡端口 ID。 
    expected_ips = set(_build_aux_ip_list(spec))  # 生成辅助网卡 IP 列表并转为集合。 
    client = _build_vpc_v3_client(region_id)  # 构建 VPC v3 客户端用于查询辅助网卡。 
    request = ListSubNetworkInterfacesRequest(parent_id=[parent_port_id])  # 按宿主端口过滤辅助网卡列表。 
    response = retry_cloud_call("查询辅助网卡详情", lambda: client.list_sub_network_interfaces(request))  # 调用 SDK 查询辅助网卡列表。
    details = []  # 初始化辅助网卡详情列表。 
    for item in response.sub_network_interfaces or []:  # 遍历辅助网卡列表。 
        if item.private_ip_address in expected_ips:  # 若 IP 在预期列表中则记录。 
            details.append({  # 追加辅助网卡详情字典。 
                "vlan_id": item.vlan_id,  # VLAN ID 字段。 
                "mac_address": item.mac_address,  # MAC 地址字段。 
                "ip_address": item.private_ip_address,  # 私网 IP 字段。 
            })  # 详情字典构建结束。 
    details.sort(key=lambda d: int(d["ip_address"].split(".")[-1]))  # 按 IP 尾段排序便于阅读。 
    return details  # 返回辅助网卡详情列表。 
