import { useCallback, useEffect, useMemo, useState } from "react"
import { Link } from "react-router-dom"
import {
  DeleteOutlined,
  EditOutlined,
  PlusOutlined,
  ReloadOutlined,
  SafetyCertificateOutlined,
  ThunderboltOutlined,
} from "@ant-design/icons"
import { Button, Card, Empty, Form, Input, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography } from "antd"
import {
  getFinanceFirewall,
  type FinanceFirewallItem,
  type FinanceFirewallResult,
  type ServerDetail,
} from "../../api/servers"
import {
  createSecurityGroupRule,
  deleteSecurityGroupRule,
  getSecurityGroup,
  linkSecurityGroup,
  listSecurityGroupRuleProtocols,
  listSecurityGroups,
  type SecurityGroupDetail,
  type SecurityGroupItem,
  type SecurityGroupRule,
  type SecurityGroupRulePayload,
  type SecurityGroupRuleProtocol,
  updateSecurityGroupRule,
  unlinkInstanceSecurityGroup,
} from "../../api/securityGroups"
import { toast } from "../../utils/toast"
import { isZjmfFinanceServer, UNSUPPORTED_OPERATION_MESSAGE } from "./helpers"

const { Text, Paragraph } = Typography

const PORT_RANGE_PATTERN = /^\d{1,5}(-\d{1,5})?$/
const IPV4_CIDR_PATTERN = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/

const FALLBACK_PROTOCOLS: SecurityGroupRuleProtocol[] = [
  { name: "全部协议", value: "all", port: "1-65535" },
  { name: "全部 TCP", value: "all_tcp", port: "1-65535" },
  { name: "全部 UDP", value: "all_udp", port: "1-65535" },
  { name: "TCP", value: "tcp", port: "" },
  { name: "UDP", value: "udp", port: "" },
  { name: "ICMP", value: "icmp", port: "1-65535" },
  { name: "SSH", value: "ssh", port: "22" },
  { name: "HTTP", value: "http", port: "80" },
  { name: "HTTPS", value: "https", port: "443" },
  { name: "RDP", value: "rdp", port: "3389" },
  { name: "MySQL", value: "mysql", port: "3306" },
  { name: "Redis", value: "redis", port: "6379" },
  { name: "PostgreSQL", value: "postgresql", port: "5432" },
  { name: "MSSQL", value: "mssql", port: "1433" },
  { name: "GRE", value: "gre", port: "1-65535" },
]

const COMMON_RULES: Array<{
  name: string
  description: string
  direction: "in" | "out"
  protocol: string
  port: string
  ip: string
}> = [
  { name: "SSH", description: "Linux 远程登录", direction: "in", protocol: "ssh", port: "22", ip: "0.0.0.0/0" },
  { name: "HTTP", description: "网站 HTTP", direction: "in", protocol: "http", port: "80", ip: "0.0.0.0/0" },
  { name: "HTTPS", description: "网站 HTTPS", direction: "in", protocol: "https", port: "443", ip: "0.0.0.0/0" },
  { name: "RDP", description: "Windows 远程桌面", direction: "in", protocol: "rdp", port: "3389", ip: "0.0.0.0/0" },
  { name: "MySQL", description: "MySQL 数据库", direction: "in", protocol: "mysql", port: "3306", ip: "0.0.0.0/0" },
  { name: "Redis", description: "Redis 数据库", direction: "in", protocol: "redis", port: "6379", ip: "0.0.0.0/0" },
  { name: "PostgreSQL", description: "PostgreSQL 数据库", direction: "in", protocol: "postgresql", port: "5432", ip: "0.0.0.0/0" },
  { name: "全部出站", description: "允许全部出站流量", direction: "out", protocol: "all", port: "1-65535", ip: "0.0.0.0/0" },
]

interface RuleFormValues {
  description?: string
  direction: "in" | "out"
  protocol: string
  port: string
  ip: string
  lock: boolean
}

function toSecurityId(value: unknown) {
  if (typeof value === "number") return value > 0 ? String(value) : undefined
  if (typeof value === "string") {
    const trimmed = value.trim()
    return trimmed && trimmed !== "0" ? trimmed : undefined
  }
  return undefined
}

function upstreamSecurityName(server: ServerDetail) {
  const value = server.upstream?.security_name
  return typeof value === "string" && value.trim() ? value : null
}

function formatDate(value?: string | null) {
  if (!value || value === "0000-00-00 00:00:00") return "-"
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return value
  return date.toLocaleString("zh-CN", { hour12: false })
}

function normalizeDirection(value?: string | null): "in" | "out" {
  if (!value) return "in"
  const normalized = value.toLowerCase()
  if (normalized === "out" || normalized === "egress" || value === "出站") return "out"
  return "in"
}

function directionLabel(value?: string | null) {
  return normalizeDirection(value) === "in" ? "入站" : "出站"
}

function protocolKey(value?: string | null) {
  return (value || "all").toLowerCase()
}

function protocolLabel(value: string | null | undefined, protocolMap: Map<string, SecurityGroupRuleProtocol>) {
  const key = protocolKey(value)
  return protocolMap.get(key)?.name || key.toUpperCase()
}

function isValidPortRange(value?: string) {
  if (!value || !PORT_RANGE_PATTERN.test(value)) return false
  const values = value.split("-").map((item) => Number(item))
  if (values.some((item) => !Number.isInteger(item) || item < 1 || item > 65535)) return false
  return values.length === 1 || values[0] <= values[1]
}

function isValidIpv4Cidr(value?: string) {
  if (!value || !IPV4_CIDR_PATTERN.test(value)) return false
  const [ip, cidr] = value.split("/")
  const segments = ip.split(".").map((item) => Number(item))
  if (segments.some((item) => !Number.isInteger(item) || item < 0 || item > 255)) return false
  if (cidr === undefined) return true
  const cidrValue = Number(cidr)
  return Number.isInteger(cidrValue) && cidrValue >= 0 && cidrValue <= 32
}

function isSameRule(rule: SecurityGroupRule, payload: Pick<SecurityGroupRulePayload, "direction" | "protocol" | "port" | "ip">) {
  return (
    normalizeDirection(rule.direction) === payload.direction
    && protocolKey(rule.protocol) === payload.protocol.toLowerCase()
    && String(rule.port || "").trim() === payload.port.trim()
    && String(rule.ip || "").trim() === payload.ip.trim()
  )
}

function errorDetail(error: unknown, fallback: string) {
  return (error as { response?: { data?: { detail?: string } } })?.response?.data?.detail || fallback
}

function formatFinanceValue(value: unknown) {
  if (value === null || value === undefined || value === "") return "-"
  return String(value)
}

function pickFinanceValue(record: FinanceFirewallItem, keys: string[]) {
  for (const key of keys) {
    const value = record[key]
    if (value !== null && value !== undefined && value !== "") return value
  }
  return null
}

export default function FirewallTab({
  server,
  onRefresh,
}: {
  server: ServerDetail
  onRefresh: () => void
}) {
  const isFinanceServer = isZjmfFinanceServer(server)
  const [ruleForm] = Form.useForm<RuleFormValues>()
  const [groups, setGroups] = useState<SecurityGroupItem[]>([])
  const [selectedId, setSelectedId] = useState<string>()
  const [localSecurityId, setLocalSecurityId] = useState<string | null | undefined>(undefined)
  const [selectedDetail, setSelectedDetail] = useState<SecurityGroupDetail | null>(null)
  const [financeFirewall, setFinanceFirewall] = useState<FinanceFirewallResult | null>(null)
  const [financeLoading, setFinanceLoading] = useState(false)
  const [protocols, setProtocols] = useState<SecurityGroupRuleProtocol[]>(FALLBACK_PROTOCOLS)
  const [loading, setLoading] = useState(true)
  const [detailLoading, setDetailLoading] = useState(false)
  const [submitting, setSubmitting] = useState(false)
  const [unlinking, setUnlinking] = useState(false)
  const [ruleOpen, setRuleOpen] = useState(false)
  const [commonOpen, setCommonOpen] = useState(false)
  const [editingRule, setEditingRule] = useState<SecurityGroupRule | null>(null)
  const [ruleSaving, setRuleSaving] = useState(false)
  const [deletingRuleId, setDeletingRuleId] = useState<number | string | null>(null)

  const currentSecurityId = toSecurityId(server.upstream?.security)
  const currentSecurityName = upstreamSecurityName(server)
  const boundSecurityId = localSecurityId === undefined ? currentSecurityId : localSecurityId || undefined
  const hasSecurityBinding = Boolean(boundSecurityId || (localSecurityId === undefined && currentSecurityName))

  const protocolMap = useMemo(
    () => new Map(protocols.map((item) => [item.value.toLowerCase(), item])),
    [protocols],
  )

  const protocolOptions = useMemo(
    () => protocols.map((item) => ({
      label: item.port ? `${item.name} (${item.port})` : item.name,
      value: item.value,
    })),
    [protocols],
  )

  const selectedGroup = useMemo(
    () => groups.find((group) => String(group.id) === selectedId),
    [groups, selectedId],
  )
  const boundGroup = useMemo(
    () => groups.find((group) => String(group.id) === boundSecurityId),
    [boundSecurityId, groups],
  )
  const shouldShowLinkButton = !hasSecurityBinding || Boolean(selectedId && selectedId !== boundSecurityId)

  const loadFinanceFirewall = useCallback(async () => {
    if (!isFinanceServer) return
    setFinanceLoading(true)
    try {
      const res = await getFinanceFirewall(server.id)
      setFinanceFirewall(res.data.data ?? { supported: false, message: UNSUPPORTED_OPERATION_MESSAGE, list: [] })
    } catch (error) {
      setFinanceFirewall({
        supported: false,
        message: errorDetail(error, UNSUPPORTED_OPERATION_MESSAGE),
        list: [],
      })
    } finally {
      setFinanceLoading(false)
    }
  }, [isFinanceServer, server.id])

  const loadGroups = useCallback(async () => {
    if (isFinanceServer) {
      setGroups([])
      setLoading(false)
      return
    }
    setLoading(true)
    try {
      const res = await listSecurityGroups({
        list_type: "all",
        per_page: 100,
        instance_id: server.id,
      })
      const list = res.data.data?.list ?? []
      setGroups(list)
      const currentInList = currentSecurityId && list.some((group) => String(group.id) === currentSecurityId)
      setSelectedId((current) => current ?? (currentInList ? currentSecurityId : undefined))
    } catch (error) {
      toast.error(errorDetail(error, "安全组列表加载失败"))
    } finally {
      setLoading(false)
    }
  }, [currentSecurityId, isFinanceServer, server.id])

  const loadProtocols = useCallback(async () => {
    if (isFinanceServer) return
    try {
      const res = await listSecurityGroupRuleProtocols()
      const list = res.data.data ?? []
      if (list.length > 0) setProtocols(list)
    } catch {
      setProtocols(FALLBACK_PROTOCOLS)
    }
  }, [isFinanceServer])

  const loadSelectedDetail = useCallback(async () => {
    if (isFinanceServer) {
      setSelectedDetail(null)
      return
    }
    if (!selectedId) {
      setSelectedDetail(null)
      return
    }
    setDetailLoading(true)
    try {
      const res = await getSecurityGroup(selectedId, true)
      setSelectedDetail(res.data.data)
    } catch (error) {
      toast.error(errorDetail(error, "安全组规则加载失败"))
      setSelectedDetail(null)
    } finally {
      setDetailLoading(false)
    }
  }, [isFinanceServer, selectedId])

  useEffect(() => {
    const timer = window.setTimeout(() => {
      if (isFinanceServer) {
        void loadFinanceFirewall()
      } else {
        void loadGroups()
        void loadProtocols()
      }
    }, 0)
    return () => window.clearTimeout(timer)
  }, [isFinanceServer, loadFinanceFirewall, loadGroups, loadProtocols])

  useEffect(() => {
    if (isFinanceServer) return
    const timer = window.setTimeout(() => {
      void loadSelectedDetail()
    }, 0)
    return () => window.clearTimeout(timer)
  }, [isFinanceServer, loadSelectedDetail])

  const options = useMemo(
    () => groups.map((group) => ({
      label: group.rule_num > 0 ? `${group.name}（${group.rule_num} 条规则）` : group.name,
      value: String(group.id),
    })),
    [groups],
  )

  const handleSubmit = async () => {
    if (!selectedId) {
      toast.info("请选择安全组")
      return
    }

    setSubmitting(true)
    try {
      await linkSecurityGroup(selectedId, { instance_id: server.id, type: 1 })
      toast.success("安全组关联成功")
      setLocalSecurityId(selectedId)
      onRefresh()
    } catch (error) {
      toast.error(errorDetail(error, "安全组关联失败"))
    } finally {
      setSubmitting(false)
    }
  }

  const handleUnlink = async () => {
    setUnlinking(true)
    try {
      await unlinkInstanceSecurityGroup(server.id)
      toast.success("安全组关联已解除")
      setLocalSecurityId(null)
      setSelectedId(undefined)
      setSelectedDetail(null)
      onRefresh()
    } catch (error) {
      toast.error(errorDetail(error, "解除关联失败"))
    } finally {
      setUnlinking(false)
    }
  }

  const defaultRuleValues = (): Partial<RuleFormValues> => ({
    direction: "in",
    ip: "0.0.0.0/0",
    lock: false,
  })

  const openCreateRuleModal = () => {
    setEditingRule(null)
    ruleForm.resetFields()
    ruleForm.setFieldsValue(defaultRuleValues())
    setRuleOpen(true)
  }

  const openEditRuleModal = (rule: SecurityGroupRule) => {
    const protocol = protocolKey(rule.protocol)
    setEditingRule(rule)
    ruleForm.setFieldsValue({
      description: rule.description || "",
      direction: normalizeDirection(rule.direction),
      protocol,
      port: rule.port || protocolMap.get(protocol)?.port || "",
      ip: rule.ip || "0.0.0.0/0",
      lock: String(rule.lock ?? 0) === "1",
    })
    setRuleOpen(true)
  }

  const handleProtocolChange = (value: string) => {
    const port = protocolMap.get(value.toLowerCase())?.port ?? ""
    ruleForm.setFieldValue("port", port)
  }

  const buildRulePayload = async (): Promise<SecurityGroupRulePayload | null> => {
    const values = await ruleForm.validateFields()
    const port = values.port.trim()
    const ip = values.ip.trim()
    if (!port) {
      ruleForm.setFields([{ name: "port", errors: ["请输入端口范围"] }])
      return null
    }
    if (!ip) {
      ruleForm.setFields([{ name: "ip", errors: ["请输入授权 IP"] }])
      return null
    }

    return {
      description: values.description?.trim() || undefined,
      direction: values.direction,
      protocol: values.protocol,
      port,
      ip,
      lock: values.lock ? 1 : 0,
    }
  }

  const handleRuleSave = async () => {
    if (!selectedId) return
    const payload = await buildRulePayload()
    if (!payload) return
    const duplicate = selectedDetail?.rule.find((rule) => {
      if (editingRule && String(rule.id) === String(editingRule.id)) return false
      return isSameRule(rule, payload)
    })
    if (duplicate) {
      toast.info("已存在相同方向、协议、端口和授权 IP 的规则")
      return
    }

    setRuleSaving(true)
    try {
      if (editingRule) {
        await updateSecurityGroupRule(editingRule.id, payload)
        toast.success("规则修改成功")
      } else {
        await createSecurityGroupRule(selectedId, payload)
        toast.success("规则添加成功")
      }
      setRuleOpen(false)
      setEditingRule(null)
      ruleForm.resetFields()
      await loadSelectedDetail()
      await loadGroups()
    } catch (error) {
      toast.error(errorDetail(error, editingRule ? "规则修改失败" : "规则添加失败"))
    } finally {
      setRuleSaving(false)
    }
  }

  const handleRuleDelete = async (rule: SecurityGroupRule) => {
    setDeletingRuleId(rule.id)
    try {
      await deleteSecurityGroupRule(rule.id)
      toast.success("规则删除成功")
      await loadSelectedDetail()
      await loadGroups()
    } catch (error) {
      toast.error(errorDetail(error, "规则删除失败"))
    } finally {
      setDeletingRuleId(null)
    }
  }

  const applyCommonRule = (preset: (typeof COMMON_RULES)[number]) => {
    setCommonOpen(false)
    setEditingRule(null)
    ruleForm.resetFields()
    ruleForm.setFieldsValue({ ...preset, lock: false })
    setRuleOpen(true)
  }

  if (isFinanceServer) {
    const list = financeFirewall?.list ?? []
    const unsupportedMessage = financeFirewall?.supported === false
      ? financeFirewall.message || UNSUPPORTED_OPERATION_MESSAGE
      : null

    return (
      <Card
        variant="outlined"
        title={<Space><SafetyCertificateOutlined /> 防火墙规则</Space>}
        extra={(
          <Button icon={<ReloadOutlined />} loading={financeLoading} onClick={() => void loadFinanceFirewall()}>
            刷新
          </Button>
        )}
      >
        {unsupportedMessage ? (
          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={unsupportedMessage} />
        ) : (
          <Table<FinanceFirewallItem>
            rowKey={(record, index) => String(record.id ?? record.name ?? index)}
            loading={financeLoading}
            dataSource={list}
            pagination={false}
            scroll={{ x: 880 }}
            locale={{
              emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无防火墙规则" />,
            }}
            columns={[
              {
                title: "名称",
                width: 180,
                render: (_, record) => formatFinanceValue(pickFinanceValue(record, ["name", "description", "remarks", "remark", "id"])),
              },
              {
                title: "方向",
                width: 100,
                render: (_, record) => formatFinanceValue(pickFinanceValue(record, ["direction", "dir", "type"])),
              },
              {
                title: "协议",
                width: 120,
                render: (_, record) => formatFinanceValue(pickFinanceValue(record, ["protocol", "protocol_name"])),
              },
              {
                title: "端口",
                width: 140,
                render: (_, record) => formatFinanceValue(pickFinanceValue(record, ["port", "ports", "port_range", "start_port"])),
              },
              {
                title: "授权 IP",
                width: 180,
                render: (_, record) => formatFinanceValue(pickFinanceValue(record, ["ip", "cidr", "source", "source_ip", "remote_ip"])),
              },
              {
                title: "状态",
                width: 100,
                render: (_, record) => formatFinanceValue(pickFinanceValue(record, ["status", "status_text"])),
              },
              {
                title: "创建时间",
                width: 180,
                render: (_, record) => formatDate(formatFinanceValue(pickFinanceValue(record, ["create_time", "created_at"]))),
              },
            ]}
          />
        )}
      </Card>
    )
  }

  if (!server.upstream_id) {
    return (
      <Card variant="outlined">
        <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="实例尚未开通，无法配置防火墙规则" />
      </Card>
    )
  }

  return (
    <Space orientation="vertical" size={16} style={{ width: "100%" }}>
      <Card variant="outlined" title={<Space><SafetyCertificateOutlined /> 防火墙规则</Space>}>
        <Space orientation="vertical" size={16} style={{ width: "100%" }}>
          <div>
            <Text strong>当前安全组</Text>
            <div style={{ marginTop: 6 }}>
              <Text type="secondary">
                {hasSecurityBinding
                  ? currentSecurityName || boundGroup?.name || (boundSecurityId ? `安全组 ${boundSecurityId}` : "已关联安全组")
                  : "未关联安全组"}
              </Text>
            </div>
          </div>

          <div>
            <Text strong>选择安全组</Text>
            <Paragraph type="secondary" style={{ marginTop: 6, marginBottom: 10 }}>
              选择一个已有安全组应用到当前实例。
            </Paragraph>
            <Space.Compact style={{ width: "100%" }}>
              <Select
                loading={loading}
                value={selectedId}
                options={options}
                placeholder="请选择安全组"
                showSearch
                optionFilterProp="label"
                style={{ width: "100%" }}
                onChange={setSelectedId}
                notFoundContent="暂无可用安全组"
              />
              {shouldShowLinkButton ? (
                <Button
                  type="primary"
                  loading={submitting}
                  disabled={!selectedId || selectedId === boundSecurityId}
                  onClick={handleSubmit}
                >
                  关联
                </Button>
              ) : null}
              {hasSecurityBinding ? (
                <Popconfirm
                  title="解除关联"
                  description="确认解除当前实例的安全组关联吗？"
                  okText="解除"
                  cancelText="取消"
                  okButtonProps={{ danger: true }}
                  onConfirm={handleUnlink}
                >
                  <Button danger loading={unlinking}>
                    解除关联
                  </Button>
                </Popconfirm>
              ) : null}
            </Space.Compact>
          </div>

          {groups.length === 0 && !loading ? (
            <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无安全组">
              <Link to="/control/security-groups">
                <Button type="primary">创建安全组</Button>
              </Link>
            </Empty>
          ) : (
            <Link to="/control/security-groups">
              <Button>管理安全组</Button>
            </Link>
          )}
        </Space>
      </Card>

      <Card
        variant="outlined"
        loading={detailLoading}
        title={`规则管理${selectedGroup ? `：${selectedGroup.name}` : ""}`}
        extra={(
          <Space>
            <Button icon={<ReloadOutlined />} disabled={!selectedId} loading={detailLoading} onClick={() => void loadSelectedDetail()}>
              刷新
            </Button>
            <Button icon={<ThunderboltOutlined />} disabled={!selectedId} onClick={() => setCommonOpen(true)}>
              常用规则
            </Button>
            <Button type="primary" icon={<PlusOutlined />} disabled={!selectedId} onClick={openCreateRuleModal}>
              添加规则
            </Button>
          </Space>
        )}
      >
        {!selectedId ? (
          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="请选择一个安全组" />
        ) : !selectedDetail || selectedDetail.rule.length === 0 ? (
          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无安全组规则" />
        ) : (
          <Space orientation="vertical" size={0} style={{ width: "100%" }}>
            {selectedDetail.rule.map((rule, index) => (
              <div
                key={String(rule.id)}
                style={{
                  minHeight: 72,
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "space-between",
                  gap: 16,
                  padding: "12px 0",
                  borderBottom: index === selectedDetail.rule.length - 1 ? "none" : "1px solid #f0f0f0",
                }}
              >
                <Space direction="vertical" size={4}>
                  <Space size={12} wrap>
                    <Tag color={normalizeDirection(rule.direction) === "in" ? "blue" : "purple"}>
                      {directionLabel(rule.direction)}
                    </Tag>
                    <Text strong>{protocolLabel(rule.protocol, protocolMap)}</Text>
                    <Text type="secondary">端口 {rule.port || "-"}</Text>
                    <Text type="secondary">授权 IP {rule.ip || "-"}</Text>
                    {String(rule.lock ?? 0) === "1" ? <Tag color="red">已锁定</Tag> : null}
                  </Space>
                  {rule.description ? (
                    <Text type="secondary" style={{ fontSize: 12 }}>备注：{rule.description}</Text>
                  ) : null}
                </Space>
                <Space size={12}>
                  <Text type="secondary">{formatDate(rule.create_time)}</Text>
                  <Button size="small" icon={<EditOutlined />} onClick={() => openEditRuleModal(rule)}>
                    修改
                  </Button>
                  <Popconfirm
                    title="删除规则"
                    description="确认删除这条安全组规则吗？"
                    okText="删除"
                    cancelText="取消"
                    okButtonProps={{ danger: true }}
                    onConfirm={() => handleRuleDelete(rule)}
                  >
                    <Button danger size="small" icon={<DeleteOutlined />} loading={deletingRuleId === rule.id}>
                      删除
                    </Button>
                  </Popconfirm>
                </Space>
              </div>
            ))}
          </Space>
        )}
      </Card>

      <Modal
        title={editingRule ? "修改规则" : "添加规则"}
        open={ruleOpen}
        okText={editingRule ? "保存" : "添加"}
        cancelText="取消"
        confirmLoading={ruleSaving}
        onOk={handleRuleSave}
        onCancel={() => {
          setRuleOpen(false)
          setEditingRule(null)
        }}
        destroyOnHidden
      >
        <Form<RuleFormValues> form={ruleForm} layout="vertical" style={{ marginTop: 20 }}>
          <Form.Item name="description" label="备注">
            <Input placeholder="请输入规则备注" allowClear />
          </Form.Item>
          <Form.Item name="direction" label="规则方向" rules={[{ required: true, message: "请选择规则方向" }]}>
            <Select
              options={[
                { label: "入站", value: "in" },
                { label: "出站", value: "out" },
              ]}
            />
          </Form.Item>
          <Form.Item name="protocol" label="协议" rules={[{ required: true, message: "请选择协议" }]}>
            <Select
              showSearch
              optionFilterProp="label"
              options={protocolOptions}
              onChange={handleProtocolChange}
              placeholder="请选择协议"
            />
          </Form.Item>
          <Form.Item
            name="port"
            label="端口范围"
            rules={[
              { required: true, message: "请输入端口范围" },
              {
                validator: (_, value: string | undefined) => (
                  isValidPortRange(value)
                    ? Promise.resolve()
                    : Promise.reject(new Error("请输入 22 或 1-65535 这样的端口范围"))
                ),
              },
            ]}
          >
            <Input placeholder="例如 22 或 1-65535" allowClear />
          </Form.Item>
          <Form.Item
            name="ip"
            label="授权 IP"
            rules={[
              { required: true, message: "请输入授权 IP" },
              {
                validator: (_, value: string | undefined) => (
                  isValidIpv4Cidr(value)
                    ? Promise.resolve()
                    : Promise.reject(new Error("请输入 0.0.0.0/0 这样的 IPv4 或 CIDR"))
                ),
              },
            ]}
          >
            <Input placeholder="例如 0.0.0.0/0" allowClear />
          </Form.Item>
          <Form.Item name="lock" label="锁定规则" valuePropName="checked">
            <Switch checkedChildren="锁定" unCheckedChildren="不锁定" />
          </Form.Item>
        </Form>
      </Modal>

      <Modal
        title="常用规则"
        open={commonOpen}
        footer={null}
        onCancel={() => setCommonOpen(false)}
        destroyOnHidden
      >
        <Space orientation="vertical" size={0} style={{ width: "100%", marginTop: 8 }}>
          {COMMON_RULES.map((preset, index) => (
            <div
              key={`${preset.protocol}-${preset.port}-${preset.direction}`}
              style={{
                minHeight: 58,
                display: "flex",
                alignItems: "center",
                justifyContent: "space-between",
                gap: 16,
                padding: "10px 0",
                borderBottom: index === COMMON_RULES.length - 1 ? "none" : "1px solid #f0f0f0",
              }}
            >
              <div>
                <Text strong>{preset.name}</Text>
                <div>
                  <Text type="secondary" style={{ fontSize: 12 }}>
                    {directionLabel(preset.direction)} · {preset.port} · {preset.ip}
                  </Text>
                </div>
              </div>
              <Button
                size="small"
                type="primary"
                disabled={selectedDetail?.rule.some((rule) => isSameRule(rule, preset)) ?? false}
                onClick={() => applyCommonRule(preset)}
              >
                {selectedDetail?.rule.some((rule) => isSameRule(rule, preset)) ? "已存在" : "选择"}
              </Button>
            </div>
          ))}
        </Space>
      </Modal>
    </Space>
  )
}
