<script setup lang="ts">
import { NCheckbox, NInput, NSelect } from 'naive-ui'
import { computed, onMounted, reactive, ref, watch } from 'vue'

import { listAvailabilityDomains, listImages, listShapes } from '@/api/instances'
import { listSubnets, listVcns } from '@/api/networks'
import { listLimits } from '@/api/tenant'
import AppInputNumber from '@/components/AppInputNumber.vue'
import FormField from '@/components/FormField.vue'
import FormSection from '@/components/FormSection.vue'
import { shortAd } from '@/composables/useFormat'
import { useAsync } from '@/composables/useAsync'
import type { ComputeShape, LimitItem } from '@/types/api'

/** 实例规格表单（创建实例弹窗与抢机任务共用）：
 *  Shape / 可用域 / 规格 / 镜像 / VCN 子网 / 引导卷 / 登录方式 / IP 分配。
 *  实例名、数量、租户 / 区域 / 区间由父表单提供，经 props 传入作数据源;
 *  sectioned 为 true 时渲染「规格 / 网络与存储 / 登录与 IP」分区标题。 */
const props = defineProps<{
  cfgId: number | null
  region?: string
  compartmentId?: string
  sectioned?: boolean
  /** 可用域字段说明;抢机任务留空为轮询语义,与手动创建(固定 AD-1)不同 */
  adHint?: string
  /** 创建场景:AD 留空(默认 AD-1)而所选 shape 在 AD-1 无配额时,自动落位首个有配额 AD;
   *  抢机表单留空 = 轮询语义,勿开 */
  autoPickAd?: boolean
}>()

/** VCN 选择的「自动创建」哨兵：提交时不传 subnetId，由后端建 VCN 与子网 */
const AUTO_VCN = '__auto__'

const blank = {
  availabilityDomain: null as string | null,
  shape: 'VM.Standard.A1.Flex',
  ocpus: 2,
  memoryInGBs: 12,
  imageId: null as string | null,
  vcnId: AUTO_VCN,
  subnetId: null as string | null,
  bootVolumeSizeGBs: null as number | null,
  bootVolumeVpusPerGB: 10,
  assignPublicIp: true,
  assignIpv6: true,
  authMode: 'password' as 'password' | 'ssh',
  randomPassword: true,
  rootPassword: '',
  sshPublicKey: '',
}
const form = reactive({ ...blank })

const images = useAsync(
  () =>
    props.cfgId === null
      ? Promise.resolve([])
      : listImages(props.cfgId, { region: props.region || undefined, shape: form.shape }),
  false,
)
const ads = useAsync(
  () =>
    props.cfgId === null
      ? Promise.resolve([])
      : listAvailabilityDomains(props.cfgId, props.region || undefined),
  false,
)
const vcns = useAsync(
  () =>
    props.cfgId === null
      ? Promise.resolve([])
      : listVcns(props.cfgId, props.region || undefined, props.compartmentId || undefined).catch(
          () => [],
        ),
  false,
)
/** 租户当前区域实际提供的 shape 清单（服务端带缓存），失败回退基础清单 */
const shapes = useAsync(
  () =>
    props.cfgId === null
      ? Promise.resolve([])
      : listShapes(props.cfgId, props.region || undefined).catch(() => []),
  false,
)
/** compute 配额一次全量拉取（服务端单页 1000 条），本地按映射统计各 shape 核数配额 */
const limits = useAsync(
  () =>
    props.cfgId === null
      ? Promise.resolve([])
      : listLimits(props.cfgId, { service: 'compute', region: props.region || undefined }).catch(
          () => [],
        ),
  false,
)
const subnets = useAsync(
  () =>
    props.cfgId === null || form.vcnId === AUTO_VCN
      ? Promise.resolve([])
      : listSubnets(props.cfgId, form.vcnId, props.region || undefined).catch(() => []),
  false,
)

function reloadAll() {
  if (props.cfgId === null) return
  void images.run()
  void ads.run()
  void vcns.run()
  void shapes.run()
  void limits.run()
}

/** 重置表单并按当前租户 / 区域重新加载数据源，父表单打开时调用 */
function reset() {
  Object.assign(form, blank)
  pendingSubnetId.value = null
  reloadAll()
}

// 弹窗 / tab 均为按需挂载（display-directive=if），挂载即自加载一次
onMounted(reset)

// 租户 / 区域 / 区间切换（抢机表单内联选择场景）：失效选择并重载
watch(
  () => [props.cfgId, props.region, props.compartmentId],
  () => {
    form.imageId = null
    form.vcnId = AUTO_VCN
    form.subnetId = null
    form.availabilityDomain = null
    reloadAll()
  },
)

watch(
  () => form.shape,
  () => {
    form.imageId = null
    if (props.cfgId !== null) void images.run()
  },
)

watch(
  () => form.vcnId,
  () => {
    form.subnetId = null
    void subnets.run()
  },
)

// 子网加载后：优先落位编辑回填的子网，否则默认选第一个
watch(
  () => subnets.data.value,
  (list) => {
    if (!list?.length) return
    if (pendingSubnetId.value && list.some((s) => s.id === pendingSubnetId.value)) {
      form.subnetId = pendingSubnetId.value
      pendingSubnetId.value = null
      return
    }
    if (!form.subnetId) form.subnetId = list[0].id
  },
)

/** 编辑任务回填：spec 为 buildSpec 产出的字段集合（payload.instance） */
const pendingSubnetId = ref<string | null>(null)

function applySpec(spec: Record<string, unknown>) {
  const s = spec as Partial<ReturnType<typeof buildSpec>> & Record<string, unknown>
  if (typeof s.shape === 'string') form.shape = s.shape
  if (typeof s.ocpus === 'number') form.ocpus = s.ocpus
  if (typeof s.memoryInGBs === 'number') form.memoryInGBs = s.memoryInGBs
  if (typeof s.availabilityDomain === 'string') form.availabilityDomain = s.availabilityDomain
  if (typeof s.imageId === 'string') form.imageId = s.imageId
  if (typeof s.bootVolumeSizeGBs === 'number') form.bootVolumeSizeGBs = s.bootVolumeSizeGBs
  if (typeof s.bootVolumeVpusPerGB === 'number') form.bootVolumeVpusPerGB = s.bootVolumeVpusPerGB
  form.assignPublicIp = s.assignPublicIp !== false
  form.assignIpv6 = s.assignIpv6 !== false
  form.authMode = typeof s.sshPublicKey === 'string' && s.sshPublicKey ? 'ssh' : 'password'
  form.sshPublicKey = typeof s.sshPublicKey === 'string' ? s.sshPublicKey : ''
  form.randomPassword = s.generateRootPassword === true || !s.rootPassword
  form.rootPassword = typeof s.rootPassword === 'string' ? s.rootPassword : ''
  pendingSubnetId.value = typeof s.subnetId === 'string' ? s.subnetId : null
}

// 回填的子网归属未知：VCN 列表加载后逐个查找包含它的 VCN 并落位
watch(
  () => vcns.data.value,
  async (list) => {
    const target = pendingSubnetId.value
    if (!target || !list?.length || props.cfgId === null) return
    for (const v of list) {
      const subs = await listSubnets(props.cfgId, v.id, props.region || undefined).catch(() => [])
      if (subs.some((s) => s.id === target)) {
        form.vcnId = v.id // 触发 subnets.run，落位在 subnets 加载 watch 中完成
        return
      }
    }
    pendingSubnetId.value = null
  },
)

/** 免费类计费类型:仅用于排序置顶,不在选项文案中标注 */
const FREE_BILLING = new Set(['ALWAYS_FREE', 'LIMITED_FREE'])

/** shape 的核数配额名:quotaNames 排除 memory / reserved / reservable 变体,
 *  剩余为核数(或实例数)限额名,与 compute limits 的 name 同名 */
function coreQuotaNames(s: ComputeShape): string[] {
  return (s.quotaNames ?? []).filter(
    (n) => !n.includes('memory') && !n.includes('reserved') && !n.includes('reservable'),
  )
}

/** 单个 shape 的配额汇总:AD 行按 AD 求和,REGION 行累计为区域总额;无匹配行返回 null */
function shapeQuotaEntry(names: Set<string>, rows: LimitItem[]): ShapeQuota | null {
  const byAd = new Map<string, number>()
  let regional: number | null = null
  let matched = false
  for (const r of rows) {
    if (!names.has(r.name)) continue
    matched = true
    if (r.scopeType === 'AD' && r.availabilityDomain) {
      byAd.set(r.availabilityDomain, (byAd.get(r.availabilityDomain) ?? 0) + Math.max(r.value, 0))
    } else if (r.scopeType === 'REGION') {
      regional = (regional ?? 0) + Math.max(r.value, 0)
    }
  }
  return matched ? { byAd, regional } : null
}

interface ShapeQuota {
  byAd: Map<string, number> // AD 全名 → 核数配额
  regional: number | null // 区域级总额,null = 无区域行
}

/** 全部 shape 的配额汇总;quotaNames 缺失或 limits 无匹配行的 shape 无条目(不判定) */
const quotaByShape = computed(() => {
  const rows = limits.data.value ?? []
  const out = new Map<string, ShapeQuota>()
  if (!rows.length) return out
  for (const s of shapes.data.value ?? []) {
    const names = new Set(coreQuotaNames(s))
    if (!names.size) continue
    const entry = shapeQuotaEntry(names, rows)
    if (entry) out.set(s.name, entry)
  }
  return out
})

/** shape 在指定 AD(null = 任一 AD)是否有配额;null = 数据不足不判定 */
function quotaInAd(name: string, ad: string | null): boolean | null {
  const q = quotaByShape.value.get(name)
  if (!q) return null
  if (q.regional !== null && q.regional <= 0) return false
  if (!q.byAd.size) return true // 仅区域级限额且 > 0
  if (ad === null) return [...q.byAd.values()].some((v) => v > 0)
  return (q.byAd.get(ad) ?? 0) > 0
}

/** 排序：免费 shape 优先 → VM.Standard 系列 → 其他 VM → 裸金属等 */
function shapeRank(s: { billingType: string; name: string }): number {
  if (FREE_BILLING.has(s.billingType)) return 0
  if (s.name.startsWith('VM.Standard')) return 1
  if (s.name.startsWith('VM.')) return 2
  return 3
}

const shapeOptions = computed(() => {
  // 清单全部来自 shapes 接口,不做本地预设;接口不可用时仍可手输(tag 模式);
  // 选定 AD 时按该 AD 判定配额,未选时看任一 AD
  const list = shapes.data.value ?? []
  const ad = form.availabilityDomain
  return [...list]
    .sort((a, b) => shapeRank(a) - shapeRank(b) || a.name.localeCompare(b.name))
    .map((s) => {
      const ok = quotaInAd(s.name, ad)
      const suffix = ok === false ? (ad ? ' · 该 AD 无配额' : ' · 无配额') : ''
      return { label: s.name + suffix, value: s.name, disabled: ok === false }
    })
})

// 清单 / 配额加载后，当前 shape 不在清单或无配额时自动切到第一个可用项
watch(
  () => shapeOptions.value,
  (opts) => {
    const cur = opts.find((o) => o.value === form.shape)
    if (!opts.length || (cur && !cur.disabled)) return
    const first = opts.find((o) => !o.disabled)
    if (first) form.shape = String(first.value)
  },
)

const selectedShape = computed(
  () => shapes.data.value?.find((s) => s.name === form.shape) ?? null,
)
/** 清单外（手输 / 未加载）的 shape 按名称后缀判定弹性 */
const isFlex = computed(() => selectedShape.value?.isFlexible ?? form.shape.endsWith('.Flex'))

const ocpuRange = computed(() => ({
  min: selectedShape.value?.ocpusMin || 1,
  max: selectedShape.value?.ocpusMax || 128,
}))
const memRange = computed(() => ({
  min: selectedShape.value?.memoryMinGBs || 1,
  max: selectedShape.value?.memoryMaxGBs || 1024,
}))

// 切换 shape 后把已填规格收敛到该 shape 的合法范围
watch(selectedShape, (s) => {
  if (!s?.isFlexible) return
  form.ocpus = Math.min(Math.max(form.ocpus, ocpuRange.value.min), ocpuRange.value.max)
  form.memoryInGBs = Math.min(Math.max(form.memoryInGBs, memRange.value.min), memRange.value.max)
})

/** 同名镜像只保留日期最新的一版：去掉尾部 -2026.05.09-0 作分组 key */
const imageOptions = computed(() => {
  const groups = new Map<string, { id: string; displayName: string; timeCreated: string }>()
  for (const img of images.data.value ?? []) {
    const key = img.displayName.replace(/-\d{4}\.\d{2}\.\d{2}-\d+$/, '')
    const prev = groups.get(key)
    if (!prev || img.timeCreated > prev.timeCreated) groups.set(key, img)
  }
  return [...groups.values()]
    .sort((a, b) => a.displayName.localeCompare(b.displayName))
    .map((i) => ({ label: i.displayName, value: i.id }))
})

/** AD 选项按当前 shape 的配额标注并禁用无配额项;数据不足时不标注 */
const adOptions = computed(
  () =>
    ads.data.value?.map((ad) => {
      const ok = quotaInAd(form.shape, ad)
      return {
        label: shortAd(ad) + (ok === false ? '（无配额）' : ''),
        value: ad,
        disabled: ok === false,
      }
    }) ?? [],
)

// 创建场景(autoPickAd):AD 留空将由后端取 AD-1;所选 shape 在 AD-1 无配额
// 而其他 AD 有配额时,自动落位首个有配额 AD(字段可见,用户可改)
watch([() => form.shape, quotaByShape, () => ads.data.value], () => {
  if (!props.autoPickAd || form.availabilityDomain !== null) return
  const list = ads.data.value ?? []
  if (!list.length || quotaInAd(form.shape, list[0]) !== false) return
  const target = list.find((ad) => quotaInAd(form.shape, ad) === true)
  if (target) form.availabilityDomain = target
})

const vcnOptions = computed(() => [
  { label: '自动创建（oci-portal-auto-vcn）', value: AUTO_VCN },
  ...(vcns.data.value ?? []).map((v) => ({ label: v.displayName, value: v.id })),
])

const subnetOptions = computed(
  () =>
    subnets.data.value?.map((s) => ({
      label: `${s.displayName}（${s.cidrBlock}${s.ipv6CidrBlock ? ' · IPv6' : ''}）`,
      value: s.id,
    })) ?? [],
)

const selectedSubnet = computed(
  () => subnets.data.value?.find((s) => s.id === form.subnetId) ?? null,
)

/** 自动创建的 VCN 默认启用 IPv6；选现有子网时看其是否有 IPv6 CIDR */
const ipv6Supported = computed(
  () => form.vcnId === AUTO_VCN || !!selectedSubnet.value?.ipv6CidrBlock,
)

// 按子网支持程度自动勾选 / 取消 IPv6
watch(ipv6Supported, (v) => {
  form.assignIpv6 = v
})

const valid = computed(() => {
  if (!form.imageId) return false
  if (form.vcnId !== AUTO_VCN && !form.subnetId) return false
  return !isFlex.value || form.ocpus > 0
})

/** 规格摘要(父表单底部预估条用):shape 短名 + 弹性核内存 */
const summary = computed(() => {
  const parts = [form.shape.replace(/^VM\.Standard\.?/, '') || form.shape]
  if (isFlex.value) parts.push(`${form.ocpus} OCPU · ${form.memoryInGBs} GB`)
  return parts.join(' · ')
})

/** 产出规格字段（不含 displayName / count / region / compartmentId，由父表单拼接） */
function buildSpec(): Record<string, unknown> {
  const usePwd = form.authMode === 'password'
  return {
    availabilityDomain: form.availabilityDomain ?? undefined,
    shape: form.shape,
    ocpus: isFlex.value ? form.ocpus : undefined,
    memoryInGBs: isFlex.value ? form.memoryInGBs : undefined,
    imageId: form.imageId ?? undefined,
    subnetId: form.vcnId === AUTO_VCN ? undefined : (form.subnetId ?? undefined),
    bootVolumeSizeGBs: form.bootVolumeSizeGBs ?? undefined,
    // 性能独立于大小：留空按镜像默认大小时同样生效（后端仅镜像启动源使用）
    bootVolumeVpusPerGB: form.bootVolumeVpusPerGB || undefined,
    assignPublicIp: form.assignPublicIp,
    assignIpv6: form.assignIpv6,
    generateRootPassword: usePwd && form.randomPassword ? true : undefined,
    rootPassword:
      usePwd && !form.randomPassword && form.rootPassword ? form.rootPassword : undefined,
    sshPublicKey:
      form.authMode === 'ssh' && form.sshPublicKey.trim() ? form.sshPublicKey.trim() : undefined,
  }
}

defineExpose({ valid, buildSpec, reset, applySpec, summary })
</script>

<template>
  <FormSection v-if="props.sectioned" title="规格" />
  <div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
    <FormField
      label="Shape"
      required
      hint="清单来自租户当前区域实际提供的 shape（无配额已标注），可手输其他"
    >
      <NSelect
        v-model:value="form.shape"
        filterable
        tag
        :options="shapeOptions"
        :loading="shapes.loading.value || limits.loading.value"
      />
    </FormField>
    <FormField label="可用域" :hint="props.adHint ?? '留空默认 AD-1'">
      <NSelect
        v-model:value="form.availabilityDomain"
        clearable
        :options="adOptions"
        :loading="ads.loading.value"
        placeholder="自动"
      />
    </FormField>
  </div>
  <div v-if="isFlex" class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
    <FormField label="OCPU" required :hint="`该 shape 可选 ${ocpuRange.min}–${ocpuRange.max}`">
      <AppInputNumber
        v-model:value="form.ocpus"
        :min="ocpuRange.min"
        :max="ocpuRange.max"
        class="w-full"
      />
    </FormField>
    <FormField label="内存（GB）" :hint="`该 shape 可选 ${memRange.min}–${memRange.max} GB`">
      <AppInputNumber
        v-model:value="form.memoryInGBs"
        :min="memRange.min"
        :max="memRange.max"
        class="w-full"
      />
    </FormField>
  </div>
  <FormField label="镜像" required hint="同名镜像只显示日期最新的一版，已按 shape 过滤">
    <NSelect
      v-model:value="form.imageId"
      filterable
      :options="imageOptions"
      :loading="images.loading.value"
      placeholder="选择启动镜像"
    />
  </FormField>

  <FormSection v-if="props.sectioned" title="网络与存储" />
  <div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
    <FormField label="VCN" hint="「自动创建」复用或创建 oci-portal-auto-vcn 公网子网,含 IPv4/IPv6 默认路由">
      <NSelect v-model:value="form.vcnId" :options="vcnOptions" :loading="vcns.loading.value" />
    </FormField>
    <FormField v-if="form.vcnId !== AUTO_VCN" label="子网" required>
      <NSelect
        v-model:value="form.subnetId"
        :options="subnetOptions"
        :loading="subnets.loading.value"
        placeholder="选择子网"
      />
    </FormField>
  </div>
  <div class="grid grid-cols-2 gap-3 max-md:grid-cols-1">
    <FormField label="引导卷（GB）" hint="留空按镜像默认（约 47 GB）">
      <AppInputNumber
        v-model:value="form.bootVolumeSizeGBs"
        :min="47"
        :max="32768"
        clearable
        class="w-full"
        placeholder="默认"
      />
    </FormField>
    <FormField label="引导卷性能（VPU/GB）" hint="10 均衡 – 120 超高性能">
      <AppInputNumber
        v-model:value="form.bootVolumeVpusPerGB"
        :min="10"
        :max="120"
        :step="10"
        class="w-full"
      />
    </FormField>
  </div>

  <FormSection v-if="props.sectioned" title="登录与 IP" />
  <FormField
    label="登录方式"
    hint="root 密码经 cloud-init 开启 root 密码登录，随机密码生成后写入实例标签 RootPassword"
  >
    <div class="grid grid-cols-2 gap-2.5 max-md:grid-cols-1">
      <button
        type="button"
        class="flex cursor-pointer items-start gap-2.5 rounded-lg border bg-white px-3 py-2.5 text-left"
        :class="
          form.authMode === 'password'
            ? 'border-accent shadow-[0_0_0_1px_var(--color-accent)]'
            : 'border-line hover:border-ink-3'
        "
        @click="form.authMode = 'password'"
      >
        <span
          class="mt-0.5 flex h-[15px] w-[15px] flex-none items-center justify-center rounded-full border-[1.5px]"
          :class="form.authMode === 'password' ? 'border-accent' : 'border-line'"
        >
          <span v-if="form.authMode === 'password'" class="h-[7px] w-[7px] rounded-full bg-accent" />
        </span>
        <span class="min-w-0">
          <span class="block text-[12.5px] font-semibold">root 密码</span>
          <span class="mt-0.5 block text-[11px] leading-snug text-ink-3">
            随机或手动设置,经 cloud-init 开启 root 登录
          </span>
        </span>
      </button>
      <button
        type="button"
        class="flex cursor-pointer items-start gap-2.5 rounded-lg border bg-white px-3 py-2.5 text-left"
        :class="
          form.authMode === 'ssh'
            ? 'border-accent shadow-[0_0_0_1px_var(--color-accent)]'
            : 'border-line hover:border-ink-3'
        "
        @click="form.authMode = 'ssh'"
      >
        <span
          class="mt-0.5 flex h-[15px] w-[15px] flex-none items-center justify-center rounded-full border-[1.5px]"
          :class="form.authMode === 'ssh' ? 'border-accent' : 'border-line'"
        >
          <span v-if="form.authMode === 'ssh'" class="h-[7px] w-[7px] rounded-full bg-accent" />
        </span>
        <span class="min-w-0">
          <span class="block text-[12.5px] font-semibold">SSH 公钥</span>
          <span class="mt-0.5 block text-[11px] leading-snug text-ink-3">
            粘贴 ssh-ed25519 / ssh-rsa 公钥,不开启密码登录
          </span>
        </span>
      </button>
    </div>
  </FormField>
  <template v-if="form.authMode === 'password'">
    <div class="mb-3.5">
      <NCheckbox v-model:checked="form.randomPassword">随机密码</NCheckbox>
    </div>
    <FormField v-if="!form.randomPassword" label="root 密码">
      <NInput
        v-model:value="form.rootPassword"
        type="password"
        show-password-on="click"
        placeholder="设置 root 登录密码"
      />
    </FormField>
  </template>
  <FormField v-else label="SSH 公钥">
    <NInput
      v-model:value="form.sshPublicKey"
      type="textarea"
      :rows="2"
      class="mono"
      placeholder="ssh-ed25519 AAAA…"
    />
  </FormField>
  <div class="flex flex-wrap gap-5">
    <NCheckbox v-model:checked="form.assignPublicIp">分配公网 IPv4</NCheckbox>
    <NCheckbox v-model:checked="form.assignIpv6" :disabled="!ipv6Supported">
      分配 IPv6{{ ipv6Supported ? '' : '（所选子网未启用）' }}
    </NCheckbox>
  </div>
</template>
