<script setup lang="ts">
import { NButton, NInput, NInputNumber, NSelect } from 'naive-ui'
import { reactive, ref } from 'vue'

import { updateSecurityList } from '@/api/networks'
import ConfirmPop from '@/components/ConfirmPop.vue'
import type { SecurityList, SecurityRule } from '@/types/api'
import { useToast } from '@/composables/useToast'

const props = defineProps<{
  cfgId: number
  list: SecurityList
  region?: string
}>()
const emit = defineEmits<{ updated: [] }>()

const message = useToast()

type Dir = 'ingress' | 'egress'

const PROTOCOLS = [
  { label: '全部', value: 'all' },
  { label: 'TCP', value: '6' },
  { label: 'UDP', value: '17' },
  { label: 'ICMP', value: '1' },
  { label: 'ICMPv6', value: '58' },
]

function protoLabel(p: string): string {
  return PROTOCOLS.find((o) => o.value === p)?.label ?? p
}

function portText(r: SecurityRule): string {
  if (r.protocol !== '6' && r.protocol !== '17') return '—'
  if (!r.portMin) return '全部'
  return r.portMax && r.portMax !== r.portMin ? `${r.portMin}-${r.portMax}` : String(r.portMin)
}

/** 编辑缓冲;idx 为 -1 表示新增行 */
const editing = ref<{ dir: Dir; idx: number } | null>(null)
const draft = reactive<SecurityRule>({ protocol: '6', isStateless: false })
const saving = ref(false)

function rulesOf(dir: Dir): SecurityRule[] {
  return dir === 'ingress' ? (props.list.ingressRules ?? []) : (props.list.egressRules ?? [])
}

function startEdit(dir: Dir, idx: number) {
  const src = idx >= 0 ? rulesOf(dir)[idx] : undefined
  Object.assign(draft, {
    protocol: src?.protocol ?? '6',
    source: src?.source,
    destination: src?.destination,
    isStateless: src?.isStateless ?? false,
    portMin: src?.portMin,
    portMax: src?.portMax,
    description: src?.description,
  })
  if (!src) {
    if (dir === 'ingress') draft.source = '0.0.0.0/0'
    else draft.destination = '0.0.0.0/0'
  }
  editing.value = { dir, idx }
}

function buildDraft(dir: Dir): SecurityRule {
  const r: SecurityRule = { protocol: draft.protocol, isStateless: false }
  if (dir === 'ingress') r.source = draft.source?.trim()
  else r.destination = draft.destination?.trim()
  if (draft.protocol === '6' || draft.protocol === '17') {
    if (draft.portMin) {
      r.portMin = draft.portMin
      r.portMax = draft.portMax || draft.portMin
    }
  }
  if (draft.description?.trim()) r.description = draft.description.trim()
  return r
}

/** 整表替换提交:规则字段一经提供即全量覆盖,两方向都要带上 */
async function submit(ingress: SecurityRule[], egress: SecurityRule[]) {
  saving.value = true
  try {
    await updateSecurityList(props.cfgId, props.list.id, {
      region: props.region,
      ingressRules: ingress,
      egressRules: egress,
    })
    message.success('规则已保存')
    editing.value = null
    emit('updated')
  } catch (e) {
    message.error(e instanceof Error ? e.message : '保存失败')
  } finally {
    saving.value = false
  }
}

function saveEditing() {
  const ed = editing.value
  if (!ed) return
  const cidr = ed.dir === 'ingress' ? draft.source : draft.destination
  if (!cidr?.trim()) {
    message.warning('CIDR 不能为空')
    return
  }
  const next = rulesOf(ed.dir).slice()
  const rule = buildDraft(ed.dir)
  if (ed.idx >= 0) next[ed.idx] = rule
  else next.push(rule)
  const ing = ed.dir === 'ingress' ? next : (props.list.ingressRules ?? [])
  const eg = ed.dir === 'egress' ? next : (props.list.egressRules ?? [])
  void submit(ing, eg)
}

function removeRule(dir: Dir, idx: number): Promise<void> {
  const next = rulesOf(dir).filter((_, i) => i !== idx)
  const ing = dir === 'ingress' ? next : (props.list.ingressRules ?? [])
  const eg = dir === 'egress' ? next : (props.list.egressRules ?? [])
  return submit(ing, eg)
}

function isEditing(dir: Dir, idx: number): boolean {
  return editing.value?.dir === dir && editing.value.idx === idx
}
</script>

<template>
  <div class="flex flex-col gap-3 py-1">
    <div v-for="dir in ['ingress', 'egress'] as const" :key="dir">
      <div class="mb-1.5 text-[11.5px] font-semibold tracking-wide text-ink-3 uppercase">
        {{ dir === 'ingress' ? '入站规则' : '出站规则' }}
      </div>
      <div class="overflow-hidden rounded-lg border border-line-soft bg-white">
        <table class="w-full border-collapse text-[12.5px]">
          <thead>
            <tr class="text-left text-xs text-ink-3">
              <th class="border-b border-line-soft px-3.5 py-1.5 font-semibold w-28">协议</th>
              <th class="border-b border-line-soft px-3.5 py-1.5 font-semibold w-44">
                {{ dir === 'ingress' ? '源' : '目标' }}
              </th>
              <th class="border-b border-line-soft px-3.5 py-1.5 font-semibold w-36">端口</th>
              <th class="border-b border-line-soft px-3.5 py-1.5 font-semibold">描述</th>
              <th class="border-b border-line-soft px-3.5 py-1.5 text-right font-semibold w-24">操作</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(r, idx) in rulesOf(dir)" :key="idx" :class="isEditing(dir, idx) ? 'bg-wash/60' : ''">
              <template v-if="isEditing(dir, idx)">
                <td class="border-b border-line-soft px-3.5 py-1.5">
                  <NSelect
                    v-model:value="draft.protocol"
                    size="tiny"
                    :options="PROTOCOLS"
                    class="!w-24"
                    :consistent-menu-width="false"
                  />
                </td>
                <td class="border-b border-line-soft px-3.5 py-1.5">
                  <NInput
                    v-if="dir === 'ingress'"
                    v-model:value="draft.source"
                    size="tiny"
                    placeholder="0.0.0.0/0"
                  />
                  <NInput v-else v-model:value="draft.destination" size="tiny" placeholder="0.0.0.0/0" />
                </td>
                <td class="border-b border-line-soft px-3.5 py-1.5">
                  <div
                    v-if="draft.protocol === '6' || draft.protocol === '17'"
                    class="flex items-center gap-1"
                  >
                    <NInputNumber
                      v-model:value="draft.portMin"
                      size="tiny"
                      :show-button="false"
                      placeholder="起"
                      class="!w-16"
                    />
                    <span class="text-ink-3">-</span>
                    <NInputNumber
                      v-model:value="draft.portMax"
                      size="tiny"
                      :show-button="false"
                      placeholder="止"
                      class="!w-16"
                    />
                  </div>
                  <span v-else class="text-ink-3">—</span>
                </td>
                <td class="border-b border-line-soft px-3.5 py-1.5">
                  <NInput v-model:value="draft.description" size="tiny" placeholder="描述(可选)" />
                </td>
                <td class="border-b border-line-soft px-3.5 py-1.5 text-right whitespace-nowrap">
                  <NButton size="tiny" type="primary" :loading="saving" @click="saveEditing">保存</NButton>
                  <NButton size="tiny" quaternary class="ml-1" :disabled="saving" @click="editing = null">
                    取消
                  </NButton>
                </td>
              </template>
              <template v-else>
                <td class="border-b border-line-soft px-3.5 py-1.5">{{ protoLabel(r.protocol) }}</td>
                <td class="mono border-b border-line-soft px-3.5 py-1.5">
                  {{ dir === 'ingress' ? r.source : r.destination }}
                </td>
                <td class="mono border-b border-line-soft px-3.5 py-1.5">{{ portText(r) }}</td>
                <td class="border-b border-line-soft px-3.5 py-1.5 text-ink-2">{{ r.description || '—' }}</td>
                <td class="border-b border-line-soft px-3.5 py-1.5 text-right whitespace-nowrap">
                  <NButton size="tiny" quaternary :disabled="!!editing" @click="startEdit(dir, idx)">
                    编辑
                  </NButton>
                  <ConfirmPop
                    title="删除该条规则？"
                    :content="`${protoLabel(r.protocol)} ${dir === 'ingress' ? r.source : r.destination} ${portText(r)}`"
                    :on-confirm="() => removeRule(dir, idx)"
                  >
                    <template #trigger>
                      <NButton size="tiny" quaternary type="error" class="ml-1" :disabled="!!editing">
                        删除
                      </NButton>
                    </template>
                  </ConfirmPop>
                </td>
              </template>
            </tr>
            <tr v-if="editing && editing.dir === dir && editing.idx === -1" class="bg-wash/60">
              <td class="border-b border-line-soft px-3.5 py-1.5">
                <NSelect
                  v-model:value="draft.protocol"
                  size="tiny"
                  :options="PROTOCOLS"
                  class="!w-24"
                  :consistent-menu-width="false"
                />
              </td>
              <td class="border-b border-line-soft px-3.5 py-1.5">
                <NInput
                  v-if="dir === 'ingress'"
                  v-model:value="draft.source"
                  size="tiny"
                  placeholder="0.0.0.0/0"
                />
                <NInput v-else v-model:value="draft.destination" size="tiny" placeholder="0.0.0.0/0" />
              </td>
              <td class="border-b border-line-soft px-3.5 py-1.5">
                <div
                  v-if="draft.protocol === '6' || draft.protocol === '17'"
                  class="flex items-center gap-1"
                >
                  <NInputNumber
                    v-model:value="draft.portMin"
                    size="tiny"
                    :show-button="false"
                    placeholder="起"
                    class="!w-16"
                  />
                  <span class="text-ink-3">-</span>
                  <NInputNumber
                    v-model:value="draft.portMax"
                    size="tiny"
                    :show-button="false"
                    placeholder="止"
                    class="!w-16"
                  />
                </div>
                <span v-else class="text-ink-3">—</span>
              </td>
              <td class="border-b border-line-soft px-3.5 py-1.5">
                <NInput v-model:value="draft.description" size="tiny" placeholder="描述(可选)" />
              </td>
              <td class="border-b border-line-soft px-3.5 py-1.5 text-right whitespace-nowrap">
                <NButton size="tiny" type="primary" :loading="saving" @click="saveEditing">保存</NButton>
                <NButton size="tiny" quaternary class="ml-1" :disabled="saving" @click="editing = null">
                  取消
                </NButton>
              </td>
            </tr>
            <tr>
              <td colspan="5" class="px-3.5 py-1.5">
                <NButton
                  size="tiny"
                  dashed
                  :disabled="!!editing"
                  @click="startEdit(dir, -1)"
                >
                  + 添加{{ dir === 'ingress' ? '入站' : '出站' }}规则
                </NButton>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>
