import ipaddress
import re
from typing import Any, Literal

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.crypto import decrypt_secret
from app.core.deps import get_current_user
from app.database import get_db
from app.models.cloud_server import CloudServer
from app.models.instance import Instance
from app.models.product import Product, ProductApiType
from app.models.user import User
from app.schemas.common import ApiResponse
from app.services.dcim_cloud_client import DcimCloudClient, DcimCloudCredentials, DcimCloudError
from app.services import zjmf_instance_adapter

router = APIRouter()
rule_router = APIRouter()
protocol_router = APIRouter()
MAX_SECURITY_GROUPS = 5
SECURITY_GROUP_ORDER_FIELDS = {"id", "name", "uid", "cloud_num", "rule_num"}
SECURITY_GROUP_TYPES = {"host", "lightHost", "hyperv", "adsl"}
SECURITY_GROUP_RULE_PROTOCOLS = {
    "all", "all_tcp", "all_udp", "tcp", "udp", "icmp", "ssh", "telnet",
    "http", "https", "mssql", "oracle", "mysql", "rdp", "postgresql",
    "redis", "gre",
}
SECURITY_GROUP_RULE_DIRECTIONS = {"in", "out"}
RULE_PORT_RE = re.compile(r"^\d{1,5}(-\d{1,5})?$")


class SecurityGroupCreateBody(BaseModel):
    name: str = Field(..., min_length=1, max_length=64)
    hostid: int | None = Field(None, ge=1)
    type: Literal["host", "lightHost", "hyperv", "adsl"] = "host"
    create_default_rule: int = Field(1, ge=0, le=1)


class SecurityGroupUpdateBody(BaseModel):
    name: str = Field(..., min_length=1, max_length=64)


class SecurityGroupRuleBody(BaseModel):
    description: str | None = Field(None, max_length=128)
    direction: Literal["in", "out"]
    protocol: str = Field(..., min_length=1, max_length=32)
    port: str = Field(..., min_length=1, max_length=64)
    ip: str = Field(..., min_length=1, max_length=64)
    lock: int = Field(0, ge=0, le=1)
    start_ip: str | None = Field(None, max_length=64)
    end_ip: str | None = Field(None, max_length=64)
    start_port: int | None = Field(None, ge=0, le=65535)
    end_port: int | None = Field(None, ge=0, le=65535)
    priority: int | None = Field(None, ge=0)
    action: Literal["accept", "drop"] | None = None


class SecurityGroupLinkBody(BaseModel):
    instance_id: int = Field(..., ge=1)
    type: int = Field(1, ge=0, le=1)


async def _get_dcim_client(
    cloud_server_id: int, db: AsyncSession
) -> tuple[DcimCloudClient, CloudServer]:
    res = await db.execute(select(CloudServer).where(CloudServer.id == cloud_server_id))
    server = res.scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=500, detail="云节点不存在")
    creds = DcimCloudCredentials(
        base_url=server.base_url,
        username=server.username,
        password=decrypt_secret(server.password_encrypted),
        account_type=server.account_type,
    )
    return DcimCloudClient(creds, server_id=server.id), server


async def _default_cloud_server(db: AsyncSession) -> CloudServer:
    res = await db.execute(
        select(CloudServer)
        .where(CloudServer.disabled.is_(False), CloudServer.client_id.is_not(None))
        .order_by(CloudServer.sort_order.asc(), CloudServer.id.asc())
    )
    server = res.scalar_one_or_none()
    if not server:
        raise HTTPException(status_code=400, detail="未配置魔方云用户ID，无法管理安全组")
    return server


async def _get_owned_instance(instance_id: int, current_user: User, db: AsyncSession) -> Instance:
    res = await db.execute(
        select(Instance).where(
            Instance.id == instance_id,
            Instance.user_id == current_user.id,
        )
    )
    instance = res.scalar_one_or_none()
    if not instance:
        raise HTTPException(status_code=404, detail="实例不存在")
    return instance


def _security_group_customer_description(current_user: User) -> str:
    return f"客户：{current_user.username}（{_security_group_customer_marker(current_user)}）"


def _security_group_customer_marker(current_user: User) -> str:
    return f"本地ID {current_user.id}"


def _to_int_or_none(value: Any) -> int | None:
    if value in (None, ""):
        return None
    try:
        return int(float(value))
    except (TypeError, ValueError):
        return None


def _response_payload(body: dict[str, Any] | None) -> Any:
    if not isinstance(body, dict):
        return {}
    return body.get("data") if "data" in body else body


def _public_security_group_item(item: dict[str, Any]) -> dict[str, Any]:
    return {
        "id": _to_int_or_none(item.get("id")) or item.get("id"),
        "name": item.get("name") or "",
        "cloud_num": _to_int_or_none(item.get("cloud_num")) or 0,
        "rule_num": _to_int_or_none(item.get("rule_num")) or 0,
        "username": item.get("username") or "",
        "create_time": item.get("create_time"),
        "type": item.get("type") or "host",
    }


def _public_security_group_rule(item: dict[str, Any]) -> dict[str, Any]:
    return {
        "id": _to_int_or_none(item.get("id")) or item.get("id"),
        "description": item.get("description") or "",
        "direction": item.get("direction") or "",
        "protocol": item.get("protocol") or "",
        "port": item.get("port") or "",
        "ip": item.get("ip") or "",
        "lock": _to_int_or_none(item.get("lock")) or 0,
        "start_ip": item.get("start_ip"),
        "end_ip": item.get("end_ip"),
        "start_port": _to_int_or_none(item.get("start_port")),
        "end_port": _to_int_or_none(item.get("end_port")),
        "priority": _to_int_or_none(item.get("priority")),
        "action": item.get("action"),
        "create_time": item.get("create_time"),
    }


def _public_security_group_detail(item: dict[str, Any]) -> dict[str, Any]:
    rules = item.get("rule") or []
    if not isinstance(rules, list):
        rules = []
    public_rules = [
        _public_security_group_rule(rule)
        for rule in rules
        if isinstance(rule, dict)
    ]
    public_item = _public_security_group_item(item)
    if public_rules:
        public_item["rule_num"] = len(public_rules)
    return {
        **public_item,
        "update_time": item.get("update_time"),
        "rule": public_rules,
    }


async def _fetch_user_security_groups(
    client: DcimCloudClient,
    server: CloudServer,
    current_user: User,
    *,
    security_type: str | None = None,
) -> list[dict[str, Any]]:
    marker = _security_group_customer_marker(current_user)
    params: dict[str, Any] = {
        "list_type": "all",
        "search": marker,
    }
    if server.client_id:
        params["user"] = server.client_id
    if security_type:
        params["type"] = security_type

    try:
        code, text, body = await client.request(
            "/security_groups",
            method="GET",
            data=params,
            timeout=10.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    payload = body or {}
    items = payload.get("data") if isinstance(payload, dict) else []
    if not isinstance(items, list):
        return []

    return [
        item
        for item in items
        if isinstance(item, dict) and marker in str(item.get("description") or "")
    ]


async def _fetch_owned_security_group(
    client: DcimCloudClient,
    server: CloudServer,
    current_user: User,
    security_group_id: int,
    *,
    get_all_rule: int = 1,
) -> dict[str, Any]:
    marker = _security_group_customer_marker(current_user)
    try:
        code, text, body = await client.request(
            f"/security_groups/{security_group_id}",
            method="GET",
            data={"get_all_rule": get_all_rule},
            timeout=10.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    item = _response_payload(body)
    if not isinstance(item, dict):
        raise HTTPException(status_code=502, detail="上游返回数据格式异常")

    if marker not in str(item.get("description") or ""):
        raise HTTPException(status_code=404, detail="安全组不存在")
    if server.client_id and _to_int_or_none(item.get("uid")) != int(server.client_id):
        raise HTTPException(status_code=404, detail="安全组不存在")

    return item


async def _find_owned_security_group_rule(
    client: DcimCloudClient,
    server: CloudServer,
    current_user: User,
    rule_id: int,
) -> tuple[dict[str, Any], dict[str, Any]]:
    groups = await _fetch_user_security_groups(client, server, current_user)
    for group in groups:
        group_id = _to_int_or_none(group.get("id"))
        if not group_id:
            continue
        detail = await _fetch_owned_security_group(
            client,
            server,
            current_user,
            group_id,
            get_all_rule=1,
        )
        rules = detail.get("rule") or []
        if not isinstance(rules, list):
            continue
        for rule in rules:
            if isinstance(rule, dict) and _to_int_or_none(rule.get("id")) == rule_id:
                return detail, rule
    raise HTTPException(status_code=404, detail="安全组规则不存在")


def _security_group_rule_payload(body: SecurityGroupRuleBody) -> dict[str, Any]:
    protocol = body.protocol.strip().lower()
    if protocol not in SECURITY_GROUP_RULE_PROTOCOLS:
        raise HTTPException(status_code=400, detail="规则协议无效")
    if body.direction not in SECURITY_GROUP_RULE_DIRECTIONS:
        raise HTTPException(status_code=400, detail="规则方向无效")

    port = body.port.strip()
    ip = body.ip.strip()
    if not _is_valid_rule_port(port):
        raise HTTPException(status_code=400, detail="端口范围无效，请输入 22 或 1-65535 这样的格式")
    if not _is_valid_rule_ip(ip):
        raise HTTPException(status_code=400, detail="授权 IP 无效，请输入 0.0.0.0/0 这样的格式")

    payload: dict[str, Any] = {
        "direction": body.direction,
        "protocol": protocol,
        "port": port,
        "ip": ip,
        "lock": body.lock,
    }
    description = (body.description or "").strip()
    payload["description"] = description
    optional_values = {
        "start_ip": body.start_ip,
        "end_ip": body.end_ip,
        "start_port": body.start_port,
        "end_port": body.end_port,
        "priority": body.priority,
        "action": body.action,
    }
    for key, value in optional_values.items():
        if value not in (None, ""):
            payload[key] = value
    return payload


def _is_valid_rule_port(value: str) -> bool:
    if not RULE_PORT_RE.match(value):
        return False
    parts = [int(part) for part in value.split("-")]
    if any(part < 1 or part > 65535 for part in parts):
        return False
    return len(parts) == 1 or parts[0] <= parts[1]


def _is_valid_rule_ip(value: str) -> bool:
    try:
        ipaddress.ip_network(value, strict=False)
        return True
    except ValueError:
        return False


@protocol_router.get("", response_model=ApiResponse)
async def list_security_group_rule_protocols(
    _current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, _ = await _get_dcim_client(server.id, db)
    try:
        code, text, body = await client.request(
            "/security_group_rule_protocols",
            method="GET",
            timeout=10.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    payload = _response_payload(body)
    if not isinstance(payload, list):
        try:
            import json as _json

            parsed = _json.loads(text.strip())
            if isinstance(parsed, list):
                payload = parsed
        except ValueError:
            pass
    if not isinstance(payload, list):
        return ApiResponse(data=[])
    return ApiResponse(data=[
        {
            "name": item.get("name") or item.get("value") or "",
            "value": item.get("value") or "",
            "port": item.get("port") or "",
        }
        for item in payload
        if isinstance(item, dict)
    ])


@router.get("", response_model=ApiResponse)
async def list_security_groups(
    page: int = Query(1, ge=1),
    per_page: int = Query(20, ge=1, le=100),
    orderby: str = Query("id"),
    sort: str = Query("desc"),
    search: str | None = Query(None),
    list_type: Literal["all", "page"] = Query("page"),
    security_type: str | None = Query(None, alias="type"),
    instance_id: int | None = Query(None, ge=1),
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    if orderby not in SECURITY_GROUP_ORDER_FIELDS:
        raise HTTPException(status_code=400, detail="排序字段无效")
    if sort not in {"asc", "desc"}:
        raise HTTPException(status_code=400, detail="排序方向无效")
    if security_type and security_type not in SECURITY_GROUP_TYPES:
        raise HTTPException(status_code=400, detail="安全组类型无效")

    if instance_id:
        instance = await _get_owned_instance(instance_id, current_user, db)
        if not instance.cloud_server_id:
            return ApiResponse(data={
                "list": [],
                "meta": {"total": 0, "total_page": 0, "page": page, "per_page": per_page},
            })
        client, server = await _get_dcim_client(instance.cloud_server_id, db)
    else:
        server = await _default_cloud_server(db)
        client, server = await _get_dcim_client(server.id, db)
    items = await _fetch_user_security_groups(
        client,
        server,
        current_user,
        security_type=security_type,
    )

    keyword = (search or "").strip().lower()
    if keyword:
        items = [item for item in items if keyword in str(item.get("name") or "").lower()]

    reverse = sort == "desc"
    if orderby == "name":
        items.sort(key=lambda item: str(item.get("name") or ""), reverse=reverse)
    else:
        items.sort(key=lambda item: _to_int_or_none(item.get(orderby)) or 0, reverse=reverse)

    total = len(items)
    if list_type == "all":
        total_page = 1 if total else 0
        page_items = items
    else:
        total_page = (total + per_page - 1) // per_page if total else 0
        start = (page - 1) * per_page
        page_items = items[start:start + per_page]

    return ApiResponse(data={
        "list": [_public_security_group_item(item) for item in page_items],
        "meta": {
            "total": total,
            "total_page": total_page,
            "page": page,
            "per_page": per_page,
        },
    })


@router.get("/{security_group_id}", response_model=ApiResponse)
async def get_security_group(
    security_group_id: int,
    get_all_rule: int = Query(1, ge=0, le=1),
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, server = await _get_dcim_client(server.id, db)
    item = await _fetch_owned_security_group(
        client,
        server,
        current_user,
        security_group_id,
        get_all_rule=get_all_rule,
    )

    return ApiResponse(data=_public_security_group_detail(item))


@router.post("/{security_group_id}/links", response_model=ApiResponse)
async def link_security_group(
    security_group_id: int,
    body: SecurityGroupLinkBody,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    instance = await _get_owned_instance(body.instance_id, current_user, db)
    if not instance.upstream_id:
        raise HTTPException(status_code=400, detail="实例尚未开通，无法关联安全组")
    if not instance.cloud_server_id:
        raise HTTPException(status_code=400, detail="实例未绑定云节点，无法关联安全组")
    if await zjmf_instance_adapter.get_zjmf_server_for_instance(db, instance):
        raise HTTPException(status_code=400, detail=zjmf_instance_adapter.UNSUPPORTED_OPERATION_MESSAGE)

    client, server = await _get_dcim_client(instance.cloud_server_id, db)
    await _fetch_owned_security_group(
        client,
        server,
        current_user,
        security_group_id,
        get_all_rule=0,
    )

    payload: list[tuple[str, Any]] = [
        ("type", body.type),
        ("cloud[]", instance.upstream_id),
    ]
    try:
        code, text, response_body = await client.request(
            f"/security_groups/{security_group_id}/links",
            method="POST",
            data=payload,  # type: ignore[arg-type]
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    return ApiResponse(data=response_payload if isinstance(response_payload, dict) else {})


@router.delete("/instance/{instance_id}/link", response_model=ApiResponse)
async def unlink_instance_security_group(
    instance_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    instance = await _get_owned_instance(instance_id, current_user, db)
    if not instance.upstream_id:
        raise HTTPException(status_code=400, detail="实例尚未开通，无法解除安全组关联")
    if not instance.cloud_server_id:
        raise HTTPException(status_code=400, detail="实例未绑定云节点，无法解除安全组关联")
    if await zjmf_instance_adapter.get_zjmf_server_for_instance(db, instance):
        raise HTTPException(status_code=400, detail=zjmf_instance_adapter.UNSUPPORTED_OPERATION_MESSAGE)

    client, _ = await _get_dcim_client(instance.cloud_server_id, db)
    try:
        code, text, response_body = await client.request(
            f"/clouds/{instance.upstream_id}/security_groups",
            method="DELETE",
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    return ApiResponse(data=response_payload if isinstance(response_payload, dict) else {})


@router.post("/{security_group_id}/rules", response_model=ApiResponse)
async def create_security_group_rule(
    security_group_id: int,
    body: SecurityGroupRuleBody,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, server = await _get_dcim_client(server.id, db)
    await _fetch_owned_security_group(
        client,
        server,
        current_user,
        security_group_id,
        get_all_rule=0,
    )
    if not server.client_id:
        raise HTTPException(status_code=400, detail="云节点未配置魔方云用户ID，无法添加规则")

    try:
        code, text, response_body = await client.request(
            f"/security_groups/{security_group_id}/rules?uid={server.client_id}",
            method="POST",
            data=_security_group_rule_payload(body),
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    return ApiResponse(data=response_payload if isinstance(response_payload, dict) else {})


@rule_router.put("/{rule_id}", response_model=ApiResponse)
async def update_security_group_rule(
    rule_id: int,
    body: SecurityGroupRuleBody,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, server = await _get_dcim_client(server.id, db)
    await _find_owned_security_group_rule(client, server, current_user, rule_id)

    try:
        code, text, response_body = await client.request(
            f"/security_group_rules/{rule_id}",
            method="PUT",
            data=_security_group_rule_payload(body),
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    return ApiResponse(data=response_payload if isinstance(response_payload, dict) else {})


@rule_router.delete("/{rule_id}", response_model=ApiResponse)
async def delete_security_group_rule(
    rule_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, server = await _get_dcim_client(server.id, db)
    await _find_owned_security_group_rule(client, server, current_user, rule_id)
    if not server.client_id:
        raise HTTPException(status_code=400, detail="云节点未配置魔方云用户ID，无法删除规则")

    try:
        code, text, response_body = await client.request(
            f"/security_group_rules/{rule_id}?uid={server.client_id}",
            method="DELETE",
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    return ApiResponse(data=response_payload if isinstance(response_payload, dict) else {})


@router.post("", response_model=ApiResponse)
async def create_security_group(
    body: SecurityGroupCreateBody,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """创建魔方云安全组。

    前端不暴露 description/uid/rid：
    - description 用于后台标识客户；
    - uid 来自云节点配置的魔方云客户 ID；
    - rid 仅在关联实例对应产品为资源包对接时传给上游。
    """
    instance: Instance | None = None
    product: Product | None = None
    server: CloudServer | None = None

    if body.hostid is not None:
        res = await db.execute(
            select(Instance, Product, CloudServer)
            .outerjoin(Product, Product.id == Instance.product_id)
            .outerjoin(CloudServer, CloudServer.id == Instance.cloud_server_id)
            .where(Instance.id == body.hostid, Instance.user_id == current_user.id)
        )
        row = res.one_or_none()
        if not row:
            raise HTTPException(status_code=404, detail="实例不存在")
        instance, product, server = row
        if not instance.upstream_id:
            raise HTTPException(status_code=400, detail="实例尚未开通，无法关联安全组")
        if not server:
            raise HTTPException(status_code=400, detail="实例未绑定云节点，无法创建安全组")
        if await zjmf_instance_adapter.get_zjmf_server_for_instance(db, instance):
            raise HTTPException(status_code=400, detail=zjmf_instance_adapter.UNSUPPORTED_OPERATION_MESSAGE)
    else:
        server = await _default_cloud_server(db)

    if not server.client_id:
        raise HTTPException(status_code=400, detail="云节点未配置魔方云用户ID，无法创建安全组")

    client, server = await _get_dcim_client(server.id, db)
    existing_groups = await _fetch_user_security_groups(client, server, current_user)
    if len(existing_groups) >= MAX_SECURITY_GROUPS:
        raise HTTPException(status_code=400, detail=f"每个用户最多创建 {MAX_SECURITY_GROUPS} 个安全组")

    name = body.name.strip()
    if not name:
        raise HTTPException(status_code=400, detail="请输入安全组名称")

    payload: dict[str, Any] = {
        "name": name,
        "description": _security_group_customer_description(current_user),
        "uid": server.client_id,
        "type": body.type,
        "create_default_rule": body.create_default_rule,
    }

    if instance and instance.upstream_id:
        payload["hostid"] = instance.upstream_id

    if (
        product
        and product.api_type == ProductApiType.DCIM_CLOUD_RID.value
        and product.api_identifier
    ):
        payload["rid"] = product.api_identifier

    try:
        code, text, response_body = await client.request(
            "/security_groups",
            method="POST",
            data=payload,
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    if not isinstance(response_payload, dict):
        response_payload = {}

    return ApiResponse(data={"id": response_payload.get("id")})


@router.put("/{security_group_id}", response_model=ApiResponse)
async def update_security_group(
    security_group_id: int,
    body: SecurityGroupUpdateBody,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, server = await _get_dcim_client(server.id, db)
    await _fetch_owned_security_group(
        client,
        server,
        current_user,
        security_group_id,
        get_all_rule=0,
    )

    name = body.name.strip()
    if not name:
        raise HTTPException(status_code=400, detail="请输入安全组名称")

    payload = {
        "name": name,
        "description": _security_group_customer_description(current_user),
    }
    try:
        code, text, response_body = await client.request(
            f"/security_groups/{security_group_id}",
            method="PUT",
            data=payload,
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    if not isinstance(response_payload, dict):
        response_payload = {}
    return ApiResponse(data=_public_security_group_item({
        **response_payload,
        "id": response_payload.get("id") or security_group_id,
        "name": response_payload.get("name") or name,
    }))


@router.delete("/{security_group_id}", response_model=ApiResponse)
async def delete_security_group(
    security_group_id: int,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    server = await _default_cloud_server(db)
    client, server = await _get_dcim_client(server.id, db)
    await _fetch_owned_security_group(
        client,
        server,
        current_user,
        security_group_id,
        get_all_rule=0,
    )
    if not server.client_id:
        raise HTTPException(status_code=400, detail="云节点未配置魔方云用户ID，无法删除安全组")

    try:
        code, text, response_body = await client.request(
            f"/security_groups/{security_group_id}?uid={server.client_id}",
            method="DELETE",
            timeout=15.0,
        )
    except DcimCloudError as e:
        raise HTTPException(status_code=502, detail=f"上游调用失败：{e}")

    if not (200 <= code < 300):
        raise HTTPException(status_code=502, detail=f"上游返回异常（HTTP {code}）：{text[:200]}")

    response_payload = _response_payload(response_body)
    return ApiResponse(data=response_payload if isinstance(response_payload, dict) else {})
