<script setup lang="ts">
import {
  NButton,
  NDataTable,
  NInput,
  NModal,
  NPopconfirm,
  NRadioButton,
  NRadioGroup,
  type DataTableColumns,
} from 'naive-ui'
import { computed, h, onUnmounted, ref } from 'vue'

import {
  createProxy,
  deleteProxy,
  importProxies,
  listProxies,
  probeProxy,
  updateProxy,
} from '@/api/proxies'
import AppInputNumber from '@/components/AppInputNumber.vue'
import FootNote from '@/components/FootNote.vue'
import FormField from '@/components/FormField.vue'
import ProxyTenantsModal from '@/components/proxy/ProxyTenantsModal.vue'
import StatusBadge from '@/components/StatusBadge.vue'
import { useAsync } from '@/composables/useAsync'
import type { ProxyImportResult, ProxyInfo } from '@/types/api'
import { useToast } from '@/composables/useToast'

const message = useToast()

const proxies = useAsync(listProxies)
const rows = computed(() => proxies.data.value?.items ?? [])

// 创建 / 导入后服务端异步探测出口地区,延迟静默刷新把结果带回来
let geoTimer: ReturnType<typeof setTimeout> | null = null
function scheduleGeoRefresh() {
  if (geoTimer) clearTimeout(geoTimer)
  geoTimer = setTimeout(() => void proxies.run({ silent: true }), 3000)
}
onUnmounted(() => {
  if (geoTimer) clearTimeout(geoTimer)
})

// ---- 新增 / 编辑弹窗 ----
const showForm = ref(false)
const editing = ref<ProxyInfo | null>(null)
const saving = ref(false)
const form = ref({ name: '', type: 'socks5', host: '', port: 1080 as number | null, username: '', password: '' })

const typeOptions = [
  { label: 'SOCKS5', value: 'socks5' },
  { label: 'HTTP', value: 'http' },
  { label: 'HTTPS', value: 'https' },
]

/** 名称留空时的自动命名预览,与后端 autoProxyName 规则一致 */
const autoNamePreview = computed(() => {
  const f = form.value
  if (f.name.trim() || !f.host.trim() || !f.port) return ''
  return `${f.type}-${f.host.trim()}:${f.port}`
})

function openCreate() {
  editing.value = null
  form.value = { name: '', type: 'socks5', host: '', port: 1080, username: '', password: '' }
  showForm.value = true
}

function openEdit(row: ProxyInfo) {
  editing.value = row
  form.value = { name: row.name, type: row.type, host: row.host, port: row.port, username: row.username, password: '' }
  showForm.value = true
}

const passwordPlaceholder = computed(() =>
  editing.value?.passwordSet ? '已配置,留空沿用;输入新值覆盖' : '无认证可留空;服务端加密存储',
)

async function save() {
  const f = form.value
  if (!f.host.trim() || !f.port) {
    message.error('请填写主机与端口')
    return
  }
  saving.value = true
  try {
    const body = {
      name: f.name.trim(),
      type: f.type,
      host: f.host.trim(),
      port: f.port,
      username: f.username.trim(),
      // 编辑时留空表示沿用已存密码;新建时留空即无密码
      ...(f.password || !editing.value ? { password: f.password } : {}),
    }
    if (editing.value) {
      await updateProxy(editing.value.id, body)
      message.success('代理已更新,关联租户的后续请求即用新配置')
    } else {
      await createProxy(body)
      message.success('代理已创建,正在检测出口地区')
    }
    showForm.value = false
    void proxies.run({ silent: true })
    scheduleGeoRefresh()
  } catch (e) {
    message.error(e instanceof Error ? e.message : '保存失败')
  } finally {
    saving.value = false
  }
}

async function remove(row: ProxyInfo) {
  try {
    await deleteProxy(row.id)
    message.success('代理已删除')
    void proxies.run({ silent: true })
  } catch (e) {
    message.error(e instanceof Error ? e.message : '删除失败')
  }
}

// ---- 关联租户管理 ----
const showTenants = ref(false)
const tenantsFor = ref<ProxyInfo | null>(null)

function openTenants(row: ProxyInfo) {
  tenantsFor.value = row
  showTenants.value = true
}

// ---- 手动重测出口地区 ----
const probingIds = ref(new Set<number>())

async function probe(row: ProxyInfo) {
  probingIds.value.add(row.id)
  try {
    const view = await probeProxy(row.id)
    message.success(view.country ? `出口地区:${view.country}${view.city ? '·' + view.city : ''}` : '探测失败,出口地区未知')
    void proxies.run({ silent: true })
  } catch (e) {
    message.error(e instanceof Error ? e.message : '重测失败')
  } finally {
    probingIds.value.delete(row.id)
  }
}

// ---- 批量导入 ----
const showImport = ref(false)
const importing = ref(false)
const importText = ref('')
const importResult = ref<ProxyImportResult | null>(null)

function openImport() {
  importText.value = ''
  importResult.value = null
  showImport.value = true
}

async function doImport() {
  if (!importText.value.trim()) {
    message.error('请粘贴代理列表')
    return
  }
  importing.value = true
  try {
    const res = await importProxies(importText.value)
    importResult.value = res
    void proxies.run({ silent: true })
    scheduleGeoRefresh()
    if (!res.failed.length) {
      message.success(`已导入 ${res.created.length} 条,正在检测出口地区`)
      showImport.value = false
    } else {
      message.warning(`成功 ${res.created.length} 条,失败 ${res.failed.length} 条,详见下方`)
    }
  } catch (e) {
    message.error(e instanceof Error ? e.message : '导入失败')
  } finally {
    importing.value = false
  }
}

/** 地区单元格:未探测显示「检测中」,探测失败显示「未知」 */
function renderGeo(row: ProxyInfo) {
  if (!row.geoAt) return h('span', { class: 'text-[12.5px] text-ink-3' }, '检测中…')
  if (!row.country) return h('span', { class: 'text-[12.5px] text-ink-3' }, '未知')
  return h('span', { class: 'text-[13px]' }, row.city ? `${row.country}·${row.city}` : row.country)
}

/** 状态单元格:与地区探测同源 —— 探测经该代理链路发起,失败即代理不可用 */
function renderStatus(row: ProxyInfo) {
  if (!row.geoAt) return h(StatusBadge, { kind: 'check', label: '检测中' })
  const ok = !!row.country
  return h(StatusBadge, { kind: ok ? 'run' : 'term', label: ok ? '正常' : '异常' })
}

// 新增 / 批量导入入口由页面标题行触发(ProxyListView),这里暴露打开方法
defineExpose({ openCreate, openImport })

const columns: DataTableColumns<ProxyInfo> = [
  {
    title: '名称',
    key: 'name',
    minWidth: 140,
    ellipsis: { tooltip: true },
    render: (row) => h('span', { class: 'text-[13px] font-medium' }, row.name),
  },
  {
    title: '类型',
    key: 'type',
    width: 84,
    render: (row) => h('span', { class: 'mono text-[12.5px] uppercase' }, row.type),
  },
  {
    title: '地址',
    key: 'host',
    minWidth: 180,
    ellipsis: { tooltip: true },
    render: (row) => h('span', { class: 'mono text-[12.5px] text-ink-2' }, `${row.host}:${row.port}`),
  },
  { title: '地区', key: 'geo', minWidth: 120, render: renderGeo },
  { title: '状态', key: 'status', width: 88, render: renderStatus },
  {
    title: '认证',
    key: 'username',
    width: 76,
    render: (row) => {
      const has = !!row.username || row.passwordSet
      // 悬停「是」可见用户名(无用户名则提示仅密码)
      const title = has ? (row.username ? `用户名 ${row.username}` : '仅密码认证') : undefined
      return h('span', { title }, [
        h(StatusBadge, { kind: has ? 'run' : 'check', label: has ? '是' : '否', dot: false }),
      ])
    },
  },
  {
    title: '关联租户',
    key: 'usedBy',
    width: 88,
    render: (row) =>
      h('span', { class: 'text-[13px] tabular-nums' }, row.usedBy ? `${row.usedBy} 个` : '—'),
  },
  {
    title: '操作',
    key: 'actions',
    width: 236,
    render: (row) =>
      h('div', { class: 'flex items-center gap-1' }, [
        h(
          NButton,
          { size: 'tiny', quaternary: true, loading: probingIds.value.has(row.id), onClick: () => probe(row) },
          () => '重测',
        ),
        h(NButton, { size: 'tiny', quaternary: true, onClick: () => openTenants(row) }, () => '关联租户'),
        h(NButton, { size: 'tiny', quaternary: true, onClick: () => openEdit(row) }, () => '编辑'),
        h(
          NPopconfirm,
          { onPositiveClick: () => remove(row) },
          {
            trigger: () =>
              h(NButton, { size: 'tiny', type: 'error', quaternary: true, disabled: row.usedBy > 0 }, () => '删除'),
            default: () => '删除后不可恢复,确认?',
          },
        ),
      ]),
  },
]
</script>

<template>
  <div class="panel">
    <NDataTable
      :columns="columns"
      :data="rows"
      :loading="proxies.loading.value"
      :scroll-x="900"
      :row-key="(r: ProxyInfo) => r.id"
    />
    <FootNote>
      支持 SOCKS5 / HTTP / HTTPS;密码 AES 加密存储、不回显明文;地区与状态为经代理实测的出口探测结果(ip-api.com),创建后自动检测,探测失败即视为异常,可点「重测」复查;被租户关联的代理不可删除,请先解除关联
    </FootNote>

    <ProxyTenantsModal
      v-model:show="showTenants"
      :proxy="tenantsFor"
      @saved="void proxies.run({ silent: true })"
    />

    <NModal
      v-model:show="showForm"
      preset="card"
      :title="editing ? '编辑代理' : '新增代理'"
      :style="{ width: '480px', maxWidth: 'calc(100vw - 24px)' }"
    >
      <FormField label="类型">
        <NRadioGroup v-model:value="form.type" class="w-full" size="small">
          <NRadioButton
            v-for="t in typeOptions"
            :key="t.value"
            :value="t.value"
            class="w-1/3 text-center"
          >
            {{ t.label }}
          </NRadioButton>
        </NRadioGroup>
      </FormField>
      <div class="grid grid-cols-3 gap-x-3">
        <FormField label="主机" required class="col-span-2">
          <NInput v-model:value="form.host" placeholder="203.0.113.50" />
        </FormField>
        <FormField label="端口" required>
          <AppInputNumber v-model:value="form.port" :min="1" :max="65535" class="!w-full" />
        </FormField>
      </div>
      <FormField
        label="名称"
        hint="选填;留空按「类型-主机:端口」自动生成,重名自动追加序号"
        :feedback="autoNamePreview ? `将生成 ${autoNamePreview}` : undefined"
      >
        <NInput v-model:value="form.name" placeholder="留空自动生成" />
      </FormField>
      <div class="grid grid-cols-2 gap-x-3">
        <FormField label="用户名">
          <NInput v-model:value="form.username" placeholder="无认证可留空" />
        </FormField>
        <FormField label="密码" :hint="passwordPlaceholder">
          <NInput
            v-model:value="form.password"
            type="password"
            show-password-on="click"
            placeholder="可选"
          />
        </FormField>
      </div>
      <div class="mb-3.5 flex items-start gap-2.5 rounded-lg border border-ok/30 bg-ok/10 px-3 py-2.5">
        <svg
          class="mt-0.5 h-[15px] w-[15px] flex-none text-ok"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <circle cx="12" cy="12" r="10" />
          <path d="M2 12h20M12 2a15.3 15.3 0 0 1 0 20 15.3 15.3 0 0 1 0-20" />
        </svg>
        <div class="min-w-0 text-xs leading-relaxed">
          <div class="font-medium">保存后自动检测出口地区</div>
          <div class="mt-0.5 text-ink-3">
            经该代理请求 ip-api.com 实测出口位置(非主机字段),结果显示在「地区」列,可随时重测
          </div>
        </div>
      </div>
      <div class="mt-1 flex items-center gap-2">
        <NButton size="small" type="primary" :loading="saving" @click="save">保存</NButton>
        <NButton size="small" quaternary @click="showForm = false">取消</NButton>
      </div>
    </NModal>

    <NModal
      v-model:show="showImport"
      preset="card"
      title="批量导入代理"
      :style="{ width: '560px', maxWidth: 'calc(100vw - 24px)' }"
    >
      <FormField label="代理列表">
        <NInput
          v-model:value="importText"
          type="textarea"
          :autosize="{ minRows: 6, maxRows: 12 }"
          placeholder="socks5://user:pass@203.0.113.50:1080&#10;https://198.51.100.2:8443&#10;203.0.113.9:1080:user:pass"
          class="mono"
        />
      </FormField>
      <div class="mb-3 flex flex-col gap-1.5 rounded-lg border border-line-soft bg-wash px-3 py-2.5 text-xs text-ink-2">
        <div class="flex flex-wrap items-center gap-2">
          <code class="mono rounded border border-line bg-white px-1.5 py-px text-[11px]">scheme://[user:pass@]host:port</code>
          <span>scheme 为 socks5 / http / https</span>
        </div>
        <div class="flex flex-wrap items-center gap-2">
          <code class="mono rounded border border-line bg-white px-1.5 py-px text-[11px]">host:port[:user:pass]</code>
          <span>无协议头,缺省按 SOCKS5;# 开头为注释</span>
        </div>
        <div class="text-ink-3">失败行回显已脱敏(凭据段替换为 ***),不阻断其余行;名称自动生成</div>
      </div>
      <div v-if="importResult" class="mb-3 overflow-hidden rounded-lg border border-line">
        <div class="flex items-center gap-4 border-b border-line-soft bg-white px-3 py-2 text-[12.5px] font-semibold">
          <span><span class="text-ok">{{ importResult.created.length }}</span> 条导入成功</span>
          <span v-if="importResult.failed.length">
            <span class="text-err">{{ importResult.failed.length }}</span> 条失败
          </span>
          <span v-if="importResult.created.length" class="ml-auto text-[11px] font-medium text-ink-3">
            正在检测出口地区…
          </span>
        </div>
        <div
          v-for="f in importResult.failed"
          :key="f.lineNo"
          class="mono flex items-center gap-2.5 bg-err/8 px-3 py-1.5 text-[12px] leading-relaxed"
        >
          <span class="flex-none font-semibold text-err">第 {{ f.lineNo }} 行</span>
          <span class="truncate text-ink">{{ f.line }}</span>
          <span class="ml-auto flex-none text-[11.5px] text-ink-2">{{ f.reason }}</span>
        </div>
      </div>
      <div class="mt-1 flex items-center gap-2">
        <NButton size="small" type="primary" :loading="importing" @click="doImport">导入</NButton>
        <NButton size="small" quaternary @click="showImport = false">关闭</NButton>
      </div>
    </NModal>
  </div>
</template>
