<template>
  <div class="container">
    <div class="page-header">
      <div class="page-title">
        <h2>IP 扫描任务</h2>
        <p class="page-subtitle">创建与维护发现任务，供自动感知页触发扫描；数据来自 DC-Control /ipscans</p>
      </div>
      <a-space>
        <a-button @click="goAutoTopology">自动感知</a-button>
        <a-button type="primary" @click="openCreate">
          <template #icon><icon-plus /></template>
          新建任务
        </a-button>
      </a-space>
    </div>

    <a-card :bordered="false" title="任务列表">
      <template #extra>
        <a-space>
          <a-input
            v-model="keyword"
            allow-clear
            placeholder="按名称搜索（keyword）"
            style="width: 220px"
            @press-enter="runSearch"
          />
          <a-button type="primary" @click="runSearch">查询</a-button>
        </a-space>
      </template>
      <a-table
        row-key="id"
        :columns="columns"
        :data="tableData"
        :loading="loading"
        :pagination="pagination"
        @page-change="onPageChange"
      >
        <template #target="{ record }">
          <span class="mono">{{ record.target_range || '—' }}</span>
        </template>
        <template #server="{ record }">
          {{ serverLabel(record.server_id) }}
        </template>
        <template #status="{ record }">
          <a-tag bordered>{{ record.status || '—' }}</a-tag>
        </template>
        <template #latestError="{ record }">
          <span v-if="latestErrorMap[record.id]" class="error-text" :title="latestErrorMap[record.id]">
            {{ latestErrorMap[record.id] }}
          </span>
          <span v-else class="text-muted">—</span>
        </template>
        <template #actions="{ record }">
          <a-space wrap>
            <a-button type="text" size="small" @click="openEdit(record)">编辑</a-button>
            <a-button type="text" size="small" @click="handleStart(record)">启动</a-button>
            <a-button type="text" size="small" @click="handleStop(record)">停止</a-button>
            <a-button type="text" size="small" @click="handleTrigger(record)">触发扫描</a-button>
            <a-popconfirm content="确定删除该任务？" @ok="handleDelete(record)">
              <a-button type="text" size="small" status="danger">删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </a-table>
    </a-card>

    <a-modal
      v-model:visible="modalVisible"
      :title="editingId ? '编辑扫描任务' : '新建扫描任务'"
      width="640px"
      unmount-on-close
      :ok-loading="submitting"
      @ok="submitForm"
      @cancel="closeModal"
    >
      <a-form :model="form" layout="vertical">
        <a-form-item label="任务名称" required>
          <a-input v-model="form.name" placeholder="唯一名称" allow-clear />
        </a-form-item>
        <a-form-item label="扫描类型" required>
          <a-select v-model="form.type" placeholder="类型">
            <a-option value="ping">ping</a-option>
            <a-option value="port">port</a-option>
            <a-option value="full">full</a-option>
          </a-select>
        </a-form-item>
        <a-form-item label="目标范围" required>
          <a-textarea
            v-model="form.target_range"
            placeholder="CIDR、IP 段等，按后端约定填写"
            :auto-size="{ minRows: 2, maxRows: 6 }"
          />
        </a-form-item>
        <a-form-item label="端口范围">
          <a-input v-model="form.port_range" placeholder="如 80,443 或 8080-8090，可选" allow-clear />
        </a-form-item>
        <a-form-item label="执行 Agent（服务器）" required>
          <a-select
            v-model="form.server_id"
            placeholder="选择已注册服务器"
            allow-search
            :loading="serversLoading"
          >
            <a-option v-for="s in servers" :key="s.id" :value="s.id">
              {{ s.name }} — {{ s.ip_address }} (#{{ s.id }})
            </a-option>
          </a-select>
        </a-form-item>
        <a-row :gutter="16">
          <a-col :span="12">
            <a-form-item label="超时（秒）">
              <a-input-number v-model="form.timeout" :min="1" :max="300" style="width: 100%" />
            </a-form-item>
          </a-col>
          <a-col :span="12">
            <a-form-item label="并发数">
              <a-input-number v-model="form.concurrency" :min="1" :max="5000" style="width: 100%" />
            </a-form-item>
          </a-col>
        </a-row>
        <a-form-item label="Cron（可选）">
          <a-input v-model="form.cron_expr" placeholder="定期扫描 Cron 表达式" allow-clear />
        </a-form-item>
        <a-form-item label="优先级">
          <a-input-number v-model="form.priority" :min="0" :max="1000" style="width: 100%" />
        </a-form-item>
        <a-form-item label="扫描配置（JSON）" extra="须为合法 JSON；可不改，默认提交 {}">
          <a-textarea
            v-model="form.config"
            placeholder="{}"
            :auto-size="{ minRows: 2, maxRows: 6 }"
          />
        </a-form-item>
        <a-form-item label="启用">
          <a-switch v-model="formEnable" />
        </a-form-item>
        <a-form-item label="描述">
          <a-input v-model="form.description" allow-clear />
        </a-form-item>
      </a-form>
    </a-modal>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { IconPlus } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue'
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
import {
  fetchIpScanList,
  createIpScan,
  updateIpScan,
  deleteIpScan,
  triggerIpScan,
  startIpScan,
  stopIpScan,
  type IpScanTask,
} from '@/api/ops/ipScan'
import { fetchServerList, type ServerItem } from '@/api/ops/server'
import { fetchDiscoveryScanRuns, type DiscoveryScanRun } from '@/api/ops/discovery'

const router = useRouter()

const loading = ref(false)
const serversLoading = ref(false)
const submitting = ref(false)
const tableData = ref<IpScanTask[]>([])
const servers = ref<ServerItem[]>([])
const latestErrorMap = ref<Record<number, string>>({})

const pagination = ref({
  current: 1,
  pageSize: 20,
  total: 0,
})

const modalVisible = ref(false)
const editingId = ref<number | null>(null)
const keyword = ref('')

const defaultForm = (): Partial<IpScanTask> => ({
  name: '',
  type: 'ping',
  description: '',
  target_range: '',
  port_range: '',
  config: '{}',
  timeout: 5,
  concurrency: 100,
  cron_expr: '',
  server_id: undefined,
  status: 'stopped',
  enable: true,
  priority: 0,
})

const form = reactive<Partial<IpScanTask>>(defaultForm())

/** 与 a-switch 绑定（form.enable 可能 undefined） */
const formEnable = computed({
  get: () => form.enable !== false,
  set: (v: boolean) => {
    form.enable = v
  },
})

/** 表单中的 config 转为提交用字符串：空或仅空白 → "{}"；否则须可 JSON.parse */
function configToSubmitJson(raw: string | undefined): { ok: true; value: string } | { ok: false } {
  const s = raw?.trim() ?? ''
  if (!s) return { ok: true, value: '{}' }
  try {
    return { ok: true, value: JSON.stringify(JSON.parse(s)) }
  } catch {
    return { ok: false }
  }
}

/** 编辑回显：无配置或空串用 "{}"；合法 JSON 则格式化便于阅读 */
function configForForm(raw?: string): string {
  const s = raw?.trim() ?? ''
  if (!s) return '{}'
  try {
    return JSON.stringify(JSON.parse(s), null, 2)
  } catch {
    return s
  }
}

function unwrapList(res: any): { total: number; data: IpScanTask[] } | undefined {
  if (!res || res.code !== 0) return undefined
  const d = res.details ?? res.data
  if (!d) return undefined
  return { total: d.total ?? 0, data: d.data ?? [] }
}

const loadServers = async () => {
  serversLoading.value = true
  try {
    const res: any = await fetchServerList({ page: 1, size: 500, is_ip_scan_server: true })
    if (res.code === 0) {
      const d = res.details || {}
      servers.value = d.data || []
    }
  } finally {
    serversLoading.value = false
  }
}

const loadTable = async () => {
  loading.value = true
  try {
    const res: any = await fetchIpScanList({
      page: pagination.value.current,
      size: pagination.value.pageSize,
      keyword: keyword.value.trim() || undefined,
    })
    const page = unwrapList(res)
    tableData.value = page?.data || []
    pagination.value.total = page?.total || 0
    await loadLatestErrorsForCurrentPage()
    if (res.code !== 0) {
      Message.error(res.message || '加载失败')
    }
  } finally {
    loading.value = false
  }
}

const loadLatestErrorsForCurrentPage = async () => {
  const rows = tableData.value || []
  if (!rows.length) {
    latestErrorMap.value = {}
    return
  }
  const entries = await Promise.all(
    rows.map(async (row) => {
      try {
        const res: any = await fetchDiscoveryScanRuns({ scan_id: row.id, page: 1, size: 1 })
        if (res?.code !== 0) return [row.id, ''] as const
        const d = (res.details ?? res.data) as { data?: DiscoveryScanRun[] } | undefined
        const last = d?.data?.[0]
        if (last?.status === 'failed' && last.error_message) {
          return [row.id, last.error_message] as const
        }
        return [row.id, ''] as const
      } catch {
        return [row.id, ''] as const
      }
    }),
  )
  latestErrorMap.value = Object.fromEntries(entries)
}

const serverLabel = (id?: number) => {
  if (!id) return '—'
  const s = servers.value.find((x) => x.id === id)
  return s ? `${s.name} (#${id})` : `#${id}`
}

const onPageChange = (current: number) => {
  pagination.value.current = current
  loadTable()
}

const runSearch = () => {
  pagination.value.current = 1
  loadTable()
}

const resetForm = () => {
  Object.assign(form, defaultForm())
}

const openCreate = () => {
  editingId.value = null
  resetForm()
  modalVisible.value = true
}

const openEdit = (row: IpScanTask) => {
  editingId.value = row.id
  Object.assign(form, {
    name: row.name,
    type: row.type,
    description: row.description || '',
    target_range: row.target_range,
    port_range: row.port_range || '',
    config: configForForm(row.config),
    timeout: row.timeout ?? 5,
    concurrency: row.concurrency ?? 100,
    cron_expr: row.cron_expr || '',
    server_id: row.server_id,
    status: row.status || 'stopped',
    enable: row.enable !== false,
    priority: row.priority ?? 0,
  })
  modalVisible.value = true
}

const closeModal = () => {
  modalVisible.value = false
}

const submitForm = async () => {
  if (!form.name?.trim()) {
    Message.warning('请填写任务名称')
    return
  }
  if (!form.type) {
    Message.warning('请选择扫描类型')
    return
  }
  if (!form.target_range?.trim()) {
    Message.warning('请填写目标范围')
    return
  }
  if (!form.server_id) {
    Message.warning('请选择执行 Agent')
    return
  }

  const cfg = configToSubmitJson(form.config)
  if (!cfg.ok) {
    Message.warning('扫描配置须为合法 JSON；不填时请保留 {}')
    return
  }

  submitting.value = true
  try {
    const body: Partial<IpScanTask> = {
      name: form.name.trim(),
      type: form.type,
      description: form.description,
      target_range: form.target_range.trim(),
      port_range: form.port_range,
      config: cfg.value,
      timeout: form.timeout,
      concurrency: form.concurrency,
      cron_expr: form.cron_expr,
      server_id: form.server_id,
      enable: form.enable !== false,
      priority: form.priority ?? 0,
    }
    if (!editingId.value) {
      body.status = 'stopped'
      const res: any = await createIpScan(body)
      if (res?.code === 0) {
        Message.success('已创建')
        modalVisible.value = false
        await loadTable()
      } else {
        Message.error(res?.message || '创建失败')
      }
    } else {
      body.status = form.status
      const res: any = await updateIpScan(editingId.value, body)
      if (res?.code === 0) {
        Message.success('已保存')
        modalVisible.value = false
        await loadTable()
      } else {
        Message.error(res?.message || '保存失败')
      }
    }
  } catch (e) {
    console.error(e)
    Message.error('请求失败')
  } finally {
    submitting.value = false
  }
}

const handleDelete = async (row: IpScanTask) => {
  try {
    const res: any = await deleteIpScan(row.id)
    if (res?.code === 0) {
      Message.success('已删除')
      await loadTable()
    } else {
      Message.error(res?.message || '删除失败')
    }
  } catch (e) {
    console.error(e)
    Message.error('删除请求失败')
  }
}

const handleTrigger = async (row: IpScanTask) => {
  try {
    const res: any = await triggerIpScan(row.id)
    if (res?.code === 0) {
      const msg = res?.details?.message || res?.data?.message
      Message.success(msg === 'scan triggered' ? '已触发扫描' : msg || '已触发扫描')
    } else {
      Message.error(res?.message || '触发失败')
    }
  } catch (e) {
    console.error(e)
    Message.error('触发失败')
  }
}

const handleStart = async (row: IpScanTask) => {
  try {
    const res: any = await startIpScan(row.id)
    if (res?.code === 0) {
      Message.success('已启动任务')
      await loadTable()
    } else {
      Message.error(res?.message || '启动失败')
    }
  } catch (e) {
    console.error(e)
    Message.error('启动失败')
  }
}

const handleStop = async (row: IpScanTask) => {
  try {
    const res: any = await stopIpScan(row.id)
    if (res?.code === 0) {
      Message.success('已停止任务')
      await loadTable()
    } else {
      Message.error(res?.message || '停止失败')
    }
  } catch (e) {
    console.error(e)
    Message.error('停止失败')
  }
}

const goAutoTopology = () => {
  router.push({ path: '/netarch/auto-topo' })
}

const columns: TableColumnData[] = [
  { title: 'ID', dataIndex: 'id', width: 72 },
  { title: '名称', dataIndex: 'name', ellipsis: true, tooltip: true },
  { title: '类型', dataIndex: 'type', width: 88 },
  { title: '目标范围', slotName: 'target', minWidth: 200 },
  { title: 'Agent', slotName: 'server', width: 180, ellipsis: true, tooltip: true },
  { title: '状态', slotName: 'status', width: 100 },
  { title: '最近错误', slotName: 'latestError', minWidth: 260, ellipsis: true, tooltip: true },
  { title: '操作', slotName: 'actions', width: 300, fixed: 'right' },
]

onMounted(async () => {
  await loadServers()
  await loadTable()
})
</script>

<script lang="ts">
export default {
  name: 'IpScanTasks',
}
</script>

<style scoped lang="less">
.container {
  padding: 20px;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 20px;

  .page-title {
    h2 {
      margin: 0;
      font-size: 20px;
      font-weight: 500;
      color: var(--color-text-1);
    }

    .page-subtitle {
      margin: 8px 0 0;
      font-size: 14px;
      color: var(--color-text-3);
    }
  }
}

.mono {
  font-family: monospace;
  font-size: 13px;
}

.error-text {
  color: #f53f3f;
}

.text-muted {
  color: var(--color-text-3);
}
</style>
