from huaweicloudsdkcore.auth.credentials import BasicCredentials  # 导入华为云 SDK 的基础鉴权对象。 
from huaweicloudsdkcore.region.region import Region  # 导入区域对象用于设置服务端点。 
from huaweicloudsdkvpc.v2 import VpcClient, CreateSecurityGroupRequest, ListSecurityGroupsRequest  # 导入 VPC 客户端与安全组请求模型。 
from huaweicloudsdkvpc.v2 import CreateSecurityGroupRuleRequest, ListSecurityGroupRulesRequest, DeleteSecurityGroupRuleRequest  # 导入安全组规则创建、查询与删除请求模型。 
from config import PROJECT_ID, get_runtime_ak_sk  # 导入鉴权参数用于构造客户端。 
from iam_service import get_project_id  # 导入自动获取项目 ID 的函数。 
from data import VPC_ENDPOINT_TEMPLATE  # 导入 VPC 服务端点模板用于拼接请求地址。 
from data import SECURITY_GROUP_NAME, SECURITY_GROUP_VPC_ID, runtime_resource_name  # 导入安全组名称与提示性 vpc_id。 
from data import SECURITY_GROUP_ENTERPRISE_PROJECT_ID, SECURITY_GROUP_AUTO_REUSE  # 导入安全组企业项目与复用配置。 
from data import SECURITY_GROUP_RULES, SECURITY_GROUP_RULE_STRICT  # 导入安全组规则白名单与严格策略配置。
from data import SECURITY_GROUP_RULE_REMOTE_IP_PREFIX, SECURITY_GROUP_RULE_ETHERTYPE  # 导入安全组规则远端网段与 IP 类型。
from retry_utils import is_transient_cloud_error, 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_client(region_id):  # 创建 VPC 客户端的函数。 
    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 客户端实例。 
    return client  # 返回构建好的客户端对象。 
def _find_security_group_by_name(region_id, sg_name, vpc_id=""):  # 按名称和VPC查找安全组的函数。 
    if not sg_name:  # 若未提供名称则无法判断是否复用。 
        return None  # 直接返回空值表示不复用。 
    client = _build_vpc_client(region_id)  # 构建 VPC 客户端用于查询。 
    request = ListSecurityGroupsRequest()  # 创建查询安全组列表的请求对象。 
    response = retry_cloud_call("查询安全组列表", lambda: client.list_security_groups(request))  # 调用 SDK 查询安全组列表。
    for sg in response.security_groups or []:  # 遍历安全组列表尝试匹配名称。 
        group_vpc_id = str(getattr(sg, "vpc_id", "") or "")
        if sg.name == sg_name and (not vpc_id or group_vpc_id == vpc_id):  # 指定VPC时必须精确匹配，避免多套资源串用安全组。 
            return sg.id  # 返回匹配到的安全组 ID。 
    return None  # 未找到匹配项则返回空值。 
def _build_security_group_body(sg_name, vpc_id=""):  # 组装安全组请求体的函数。 
    sg_body = {  # 创建安全组请求体字典。 
        "name": sg_name,  # 安全组名称字段。 
        "vpc_id": vpc_id or SECURITY_GROUP_VPC_ID or None,  # 多套资源绑定各自VPC。 
        "enterprise_project_id": SECURITY_GROUP_ENTERPRISE_PROJECT_ID,  # 企业项目 ID 字段。 
    }  # 安全组请求体初始字典定义结束。 
    filtered_body = {k: v for k, v in sg_body.items() if v not in (None, "", [], {})}  # 过滤空值字段避免接口报错。 
    return filtered_body  # 返回过滤后的请求体字典。 
def _rule_protocol(value):  # 统一 GRE 名称与 IP 协议号，兼容云端历史返回格式。
    protocol = str(value or "").strip().lower()
    if protocol == "gre":
        return "47"
    return protocol or None
def _rule_key(direction, protocol, port_min, port_max, ethertype, remote_ip_prefix):  # 构造规则唯一键用于去重。
    return (str(direction or "").lower(), _rule_protocol(protocol), port_min, port_max, ethertype, remote_ip_prefix)  # 返回规则键元组。
def _rule_port(value):  # 统一已有规则端口类型，避免字符串和数字造成误判。
    try:
        return int(value)
    except (TypeError, ValueError):
        return None
def _list_existing_rules(client, security_group_id):  # 查询安全组现有规则并生成规则列表。
    request = ListSecurityGroupRulesRequest(security_group_id=security_group_id)  # 创建安全组规则列表请求对象。 
    response = retry_cloud_call("查询安全组规则", lambda: client.list_security_group_rules(request))  # 调用 SDK 查询安全组规则列表。
    rules = []  # 初始化规则列表用于记录规则键与 ID。 
    for rule in response.security_group_rules or []:  # 遍历已有规则列表。 
        key = _rule_key(rule.direction, rule.protocol, _rule_port(rule.port_range_min), _rule_port(rule.port_range_max), rule.ethertype, rule.remote_ip_prefix)  # 构造已有规则键。
        rules.append({"id": rule.id, "key": key})  # 保存规则 ID 与规则键。 
    return rules  # 返回已有规则列表。 
def _build_rule_body(security_group_id, direction, rule):  # 组装安全组规则请求体的函数。 
    protocol = rule.get("protocol")  # 读取规则协议字段。 
    port_min = rule.get("port_range_min")  # 读取规则起始端口字段。 
    port_max = rule.get("port_range_max")  # 读取规则结束端口字段。 
    rule_body = {  # 创建安全组规则请求体字典。 
        "security_group_id": security_group_id,  # 规则所属安全组 ID 字段。 
        "direction": direction,  # 规则方向字段（ingress/egress）。 
        "ethertype": SECURITY_GROUP_RULE_ETHERTYPE,  # 规则 IP 类型字段。 
        "protocol": protocol,  # 规则协议字段（可为 IP 协议号）。 
        "port_range_min": port_min,  # 规则起始端口字段，GRE 等协议可为空。 
        "port_range_max": port_max,  # 规则结束端口字段，GRE 等协议可为空。 
        "remote_ip_prefix": SECURITY_GROUP_RULE_REMOTE_IP_PREFIX,  # 规则远端网段字段。 
    }  # 安全组规则请求体字典定义结束。 
    filtered_body = {k: v for k, v in rule_body.items() if v not in (None, "", [], {})}  # 过滤空值字段避免接口报错。 
    return filtered_body  # 返回过滤后的规则请求体字典。 
def _rule_directions(rule):  # 读取规则方向；默认只处理入方向，避免继续创建出方向全开规则。
    directions = rule.get("directions") or (rule.get("direction") or "ingress",)
    if isinstance(directions, str):
        return (directions,)
    return tuple(directions)
def _build_desired_rule_keys():  # 构建目标规则键集合的函数。 
    desired_keys = set()  # 初始化目标规则键集合。 
    for rule in SECURITY_GROUP_RULES:  # 遍历规则白名单配置。 
        protocol = rule.get("protocol")  # 获取协议字段。 
        port_min = rule.get("port_range_min")  # 获取端口起始值。 
        port_max = rule.get("port_range_max")  # 获取端口结束值。 
        for direction in _rule_directions(rule):  # 按规则配置方向放行。 
            key = _rule_key(direction, protocol, port_min, port_max, SECURITY_GROUP_RULE_ETHERTYPE, SECURITY_GROUP_RULE_REMOTE_IP_PREFIX)  # 构造目标规则键。 
            desired_keys.add(key)  # 将目标规则键加入集合。 
    return desired_keys  # 返回目标规则键集合。 
def _build_desired_rule_bodies(security_group_id):  # 构建目标规则请求体列表的函数。 
    desired_rules = []  # 初始化目标规则请求体列表。 
    for rule in SECURITY_GROUP_RULES:  # 遍历规则白名单配置。 
        for direction in _rule_directions(rule):  # 按规则配置方向放行。 
            body = _build_rule_body(security_group_id, direction, rule)  # 构建规则请求体。 
            key = _rule_key(direction, rule.get("protocol"), rule.get("port_range_min"), rule.get("port_range_max"), SECURITY_GROUP_RULE_ETHERTYPE, SECURITY_GROUP_RULE_REMOTE_IP_PREFIX)  # 构造规则键。 
            desired_rules.append({"key": key, "body": body})  # 保存规则键与请求体。 
    return desired_rules  # 返回目标规则请求体列表。 
def _ensure_security_group_rules(region_id, security_group_id):  # 确保安全组包含所需端口规则的函数。 
    client = _build_vpc_client(region_id)  # 构建 VPC 客户端用于规则查询与创建。 
    existing_rules = _list_existing_rules(client, security_group_id)  # 获取已有规则列表。 
    existing_keys = {item["key"] for item in existing_rules}  # 构造已有规则键集合。 
    desired_keys = _build_desired_rule_keys()  # 构造目标规则键集合。 
    if SECURITY_GROUP_RULE_STRICT:  # 若开启严格模式则删除多余规则。 
        for item in existing_rules:  # 遍历已有规则列表。 
            if item["key"][0] != "ingress":  # 只清理入方向，出方向保持云默认或原有配置。 
                continue
            if item["key"] in desired_keys:  # 若规则在白名单中则保留。 
                continue  # 继续处理下一条规则。 
            try:  # 捕获删除规则时的异常避免中断。 
                delete_request = DeleteSecurityGroupRuleRequest(security_group_rule_id=item["id"])  # 构建删除规则请求。 
                retry_cloud_call("删除安全组规则", lambda: client.delete_security_group_rule(delete_request))  # 调用 SDK 删除多余规则。
            except Exception:  # 若删除失败则忽略错误继续执行。 
                continue  # 删除失败时跳过避免影响流程。 
    desired_rules = _build_desired_rule_bodies(security_group_id)  # 构建目标规则请求体列表。 
    for item in desired_rules:  # 遍历目标规则请求体列表。 
        if item["key"] in existing_keys:  # 若规则已存在则跳过创建。 
            continue  # 进入下一条规则。 
        request = CreateSecurityGroupRuleRequest()  # 创建安全组规则请求对象。 
        request.body = {"security_group_rule": item["body"]}  # 填充请求体以符合 API 结构要求。 
        retry_cloud_call("创建安全组规则", lambda: client.create_security_group_rule(request))  # 调用 SDK 创建安全组规则。
        existing_keys.add(item["key"])  # 将新建规则加入集合避免重复创建。 
def create_security_group(region_id, vpc_id=""):  # 创建安全组的函数实现。 
    sg_name = runtime_resource_name(SECURITY_GROUP_NAME)  # 多套资源按编号使用独立安全组。
    if SECURITY_GROUP_AUTO_REUSE:  # 若开启复用则优先查找同名安全组。 
        existed_id = _find_security_group_by_name(region_id, sg_name, vpc_id)  # 按名称和VPC查找是否已有安全组。 
        if existed_id:  # 若找到则直接返回现有安全组 ID。 
            _ensure_security_group_rules(region_id, existed_id)  # 复用时也确保端口规则已设置。 
            return existed_id  # 返回复用的安全组 ID。 
    client = _build_vpc_client(region_id)  # 构建 VPC 客户端以调用创建接口。 
    sg_body = _build_security_group_body(sg_name, vpc_id)  # 构建安全组请求体。 
    request = CreateSecurityGroupRequest()  # 创建安全组请求对象。 
    request.body = {"security_group": sg_body}  # 填充请求体以符合 API 结构要求。 
    # 安全组创建接口遇到临时网络异常时，先反查同名安全组，避免重复创建。
    for attempt in range(1, 4):
        try:
            response = retry_cloud_call("创建安全组", lambda: client.create_security_group(request), attempts=1)
            _ensure_security_group_rules(region_id, response.security_group.id)  # 创建后确保端口规则已设置。
            return response.security_group.id  # 返回新建安全组的 ID。
        except Exception as exc:
            if not is_transient_cloud_error(exc) or attempt >= 3:
                raise
            existed_id = _find_security_group_by_name(region_id, sg_name, vpc_id)
            if existed_id:
                _ensure_security_group_rules(region_id, existed_id)
                return existed_id
            print(f"步骤: 创建安全组临时异常，重试 {attempt + 1}/3: {exc}")
    raise RuntimeError("创建安全组失败")
