<script setup>
import { computed, onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

import {
  banIp,
  banUser,
  cleanup,
  clearIpRisk,
  getBannedIps,
  getBannedUsers,
  getDashboard,
  getIpRisk,
  getThreats,
  getUserRisk,
  unbanIp,
  unbanUser,
} from '../api/security'
import MetricGrid from '../components/MetricGrid.vue'

const pageSize = 20

const activeTab = ref('threats')

const dashboardLoading = ref(false)
const dashboard = ref(null)

const threatsLoading = ref(false)
const threatItems = ref([])
const threatTotal = ref(0)
const threatPage = ref(1)
const threatTypeFilter = ref('')
const threatSeverityFilter = ref('')

const bansLoading = ref(false)
const bannedIps = ref([])
const bannedUsers = ref([])
const banTab = ref('ips')

const banDialogOpen = ref(false)
const banSubmitting = ref(false)
const banForm = ref({
  kind: 'ip',
  ip: '',
  user_id: '',
  reason: '',
  duration_hours: 24,
  permanent: false,
})

const riskTab = ref('ip')
const riskLoading = ref(false)
const riskIpInput = ref('')
const riskUserIdInput = ref('')
const riskResult = ref(null)
const riskResultKind = ref('')

const commonThreatTypes = [
  'sql_injection',
  'xss',
  'path_traversal',
  'command_injection',
  'ssrf',
  'scanner',
  'bruteforce',
  'csrf',
  'xxe',
  'file_upload',
]

function normalizeCount(value) {
  const n = Number(value)
  return Number.isFinite(n) ? n : 0
}

function scoreMeta(score) {
  const n = Number(score || 0)
  if (n >= 80) return { label: '高', type: 'danger' }
  if (n >= 50) return { label: '中', type: 'warning' }
  return { label: '低', type: 'success' }
}

function formatExpires(expiresAt) {
  const text = String(expiresAt || '').trim()
  return text ? text : '永久'
}

function payloadTooltip(row) {
  const parts = []
  if (row?.field_name) parts.push(`字段: ${row.field_name}`)
  if (row?.rule) parts.push(`规则: ${row.rule}`)
  if (row?.matched) parts.push(`匹配: ${row.matched}`)
  if (row?.value_preview) parts.push(`值: ${row.value_preview}`)
  return parts.length ? parts.join(' · ') : '-'
}

function pathText(row) {
  const method = String(row?.request_method || '').trim()
  const path = String(row?.request_path || '').trim()
  const combined = `${method} ${path}`.trim()
  return combined || '-'
}

const threatTypeOptions = computed(() => {
  const seen = new Set(commonThreatTypes)
  const recent = dashboard.value?.recent_threat_events || []
  for (const item of recent) {
    const t = String(item?.threat_type || '').trim()
    if (t) seen.add(t)
  }
  for (const item of threatItems.value || []) {
    const t = String(item?.threat_type || '').trim()
    if (t) seen.add(t)
  }
  return Array.from(seen)
    .sort((a, b) => a.localeCompare(b))
    .map((t) => ({ label: t, value: t }))
})

const dashboardCards = computed(() => {
  const d = dashboard.value || {}
  return [
    {
      key: 'threat_events_24h',
      label: '最近24小时威胁事件',
      value: normalizeCount(d.threat_events_24h),
      tone: 'red',
      hint: '用于衡量当前攻击面活跃度',
    },
    {
      key: 'banned_ip_count',
      label: '当前封禁 IP 数',
      value: normalizeCount(d.banned_ip_count),
      tone: 'orange',
      hint: '自动与人工封禁总量',
    },
    {
      key: 'banned_user_count',
      label: '当前封禁用户数',
      value: normalizeCount(d.banned_user_count),
      tone: 'purple',
      hint: '高风险账户拦截情况',
    },
  ]
})

const threatTotalPages = computed(() => Math.max(1, Math.ceil((threatTotal.value || 0) / pageSize)))

async function loadDashboard() {
  dashboardLoading.value = true
  try {
    dashboard.value = await getDashboard()
  } catch {
    dashboard.value = null
  } finally {
    dashboardLoading.value = false
  }
}

async function loadThreats() {
  threatsLoading.value = true
  try {
    const params = {
      page: threatPage.value,
      per_page: pageSize,
    }
    if (threatTypeFilter.value) params.event_type = threatTypeFilter.value
    if (threatSeverityFilter.value) params.severity = threatSeverityFilter.value

    const data = await getThreats(params)
    threatItems.value = data?.items || []
    threatTotal.value = data?.total || 0
  } catch {
    threatItems.value = []
    threatTotal.value = 0
  } finally {
    threatsLoading.value = false
  }
}

async function loadBans() {
  if (bansLoading.value) return
  bansLoading.value = true
  try {
    const [ipsRes, usersRes] = await Promise.allSettled([getBannedIps(), getBannedUsers()])
    bannedIps.value = ipsRes.status === 'fulfilled' ? ipsRes.value?.items || [] : []
    bannedUsers.value = usersRes.status === 'fulfilled' ? usersRes.value?.items || [] : []
  } finally {
    bansLoading.value = false
  }
}

async function refreshAll() {
  await Promise.allSettled([loadDashboard(), loadThreats(), loadBans()])
}

function onThreatFilter() {
  threatPage.value = 1
  loadThreats()
}

function onThreatReset() {
  threatTypeFilter.value = ''
  threatSeverityFilter.value = ''
  threatPage.value = 1
  loadThreats()
}

function resetBanForm() {
  banForm.value = {
    kind: 'ip',
    ip: '',
    user_id: '',
    reason: '',
    duration_hours: 24,
    permanent: false,
  }
}

function openBanDialog(kind = 'ip', preset = {}) {
  resetBanForm()
  banForm.value.kind = kind === 'user' ? 'user' : 'ip'
  if (banForm.value.kind === 'ip') {
    banForm.value.ip = String(preset.ip || '').trim()
  } else {
    banForm.value.user_id = String(preset.user_id || '').trim()
  }
  if (preset.reason) banForm.value.reason = String(preset.reason || '').trim()
  banDialogOpen.value = true
}

async function submitBan() {
  const kind = banForm.value.kind
  const reason = String(banForm.value.reason || '').trim()
  const permanent = Boolean(banForm.value.permanent)
  const durationHours = Number(banForm.value.duration_hours || 24)

  if (!reason) {
    ElMessage.error('原因不能为空')
    return
  }

  if (kind === 'ip') {
    const ip = String(banForm.value.ip || '').trim()
    if (!ip) {
      ElMessage.error('IP不能为空')
      return
    }
    banSubmitting.value = true
    try {
      await banIp({ ip, reason, duration_hours: durationHours, permanent })
      ElMessage.success('IP已封禁')
      banDialogOpen.value = false
      await Promise.allSettled([loadDashboard(), loadBans()])
    } catch {
      // handled by interceptor
    } finally {
      banSubmitting.value = false
    }
    return
  }

  const userIdRaw = String(banForm.value.user_id || '').trim()
  const userId = Number.parseInt(userIdRaw, 10)
  if (!Number.isFinite(userId)) {
    ElMessage.error('用户ID无效')
    return
  }

  banSubmitting.value = true
  try {
    await banUser({ user_id: userId, reason, duration_hours: durationHours, permanent })
    ElMessage.success('用户已封禁')
    banDialogOpen.value = false
    await Promise.allSettled([loadDashboard(), loadBans()])
  } catch {
    // handled by interceptor
  } finally {
    banSubmitting.value = false
  }
}

async function onUnbanIp(ip) {
  const ipText = String(ip || '').trim()
  if (!ipText) return

  try {
    await ElMessageBox.confirm(`确定解除对 IP ${ipText} 的封禁吗？`, '解除封禁', {
      confirmButtonText: '解除',
      cancelButtonText: '取消',
      type: 'warning',
    })
  } catch {
    return
  }

  try {
    await unbanIp(ipText)
    ElMessage.success('已解除IP封禁')
    await Promise.allSettled([loadDashboard(), loadBans()])
  } catch {
    // handled by interceptor
  }
}

async function onUnbanUser(userId) {
  const id = Number.parseInt(String(userId || '').trim(), 10)
  if (!Number.isFinite(id)) return

  try {
    await ElMessageBox.confirm(`确定解除对 用户ID ${id} 的封禁吗？`, '解除封禁', {
      confirmButtonText: '解除',
      cancelButtonText: '取消',
      type: 'warning',
    })
  } catch {
    return
  }

  try {
    await unbanUser(id)
    ElMessage.success('已解除用户封禁')
    await Promise.allSettled([loadDashboard(), loadBans()])
  } catch {
    // handled by interceptor
  }
}

function jumpToIpRisk(ip) {
  const ipText = String(ip || '').trim()
  if (!ipText) return
  activeTab.value = 'risk'
  riskTab.value = 'ip'
  riskIpInput.value = ipText
  queryIpRisk()
}

function jumpToUserRisk(userId) {
  const idText = String(userId || '').trim()
  if (!idText) return
  activeTab.value = 'risk'
  riskTab.value = 'user'
  riskUserIdInput.value = idText
  queryUserRisk()
}

async function queryIpRisk() {
  const ip = String(riskIpInput.value || '').trim()
  if (!ip) {
    ElMessage.error('请输入IP')
    return
  }
  riskLoading.value = true
  try {
    riskResult.value = await getIpRisk(ip)
    riskResultKind.value = 'ip'
  } catch {
    riskResult.value = null
    riskResultKind.value = ''
  } finally {
    riskLoading.value = false
  }
}

async function queryUserRisk() {
  const raw = String(riskUserIdInput.value || '').trim()
  const userId = Number.parseInt(raw, 10)
  if (!Number.isFinite(userId)) {
    ElMessage.error('请输入有效的用户ID')
    return
  }
  riskLoading.value = true
  try {
    riskResult.value = await getUserRisk(userId)
    riskResultKind.value = 'user'
  } catch {
    riskResult.value = null
    riskResultKind.value = ''
  } finally {
    riskLoading.value = false
  }
}

function openBanFromRisk() {
  if (!riskResult.value || !riskResultKind.value) return
  if (riskResultKind.value === 'ip') {
    openBanDialog('ip', { ip: riskResult.value?.ip, reason: '风险查询手动封禁' })
  } else {
    openBanDialog('user', { user_id: riskResult.value?.user_id, reason: '风险查询手动封禁' })
  }
}

async function unbanFromRisk() {
  if (!riskResult.value || !riskResultKind.value) return
  if (riskResultKind.value === 'ip') {
    await onUnbanIp(riskResult.value?.ip)
    await queryIpRisk()
  } else {
    await onUnbanUser(riskResult.value?.user_id)
    await queryUserRisk()
  }
}

async function clearIpRiskScore() {
  if (riskResultKind.value !== 'ip') return
  const ipText = String(riskResult.value?.ip || '').trim()
  if (!ipText) return

  try {
    await ElMessageBox.confirm(
      `确定清除 IP ${ipText} 的风险分吗？\n\n清除风险分不会删除威胁历史，也不会解除封禁。`,
      '清除风险分',
      { confirmButtonText: '清除', cancelButtonText: '取消', type: 'warning' },
    )
  } catch {
    return
  }

  if (riskLoading.value) return
  riskLoading.value = true
  try {
    await clearIpRisk(ipText)
    ElMessage.success('IP风险分已清零')
  } catch {
    // handled by interceptor
  } finally {
    riskLoading.value = false
  }

  await queryIpRisk()
}

const cleanupLoading = ref(false)

async function onCleanup() {
  try {
    await ElMessageBox.confirm(
      '确定清理过期封禁记录，并衰减风险分吗？\n\n该操作不会影响仍在有效期内的封禁。',
      '清理过期记录',
      { confirmButtonText: '清理', cancelButtonText: '取消', type: 'warning' },
    )
  } catch {
    return
  }

  cleanupLoading.value = true
  try {
    await cleanup()
    ElMessage.success('清理完成')
    await refreshAll()
  } catch {
    // handled by interceptor
  } finally {
    cleanupLoading.value = false
  }
}

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

<template>
  <div class="page-stack">
    <div class="app-page-title">
      <h2>安全防护</h2>
      <div class="toolbar">
        <el-button type="warning" plain :loading="cleanupLoading" @click="onCleanup">清理过期记录</el-button>
        <el-button type="primary" @click="openBanDialog()">手动封禁</el-button>
      </div>
    </div>

    <MetricGrid :items="dashboardCards" :loading="dashboardLoading" :min-width="220" />

    <el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
      <el-tabs v-model="activeTab">
        <el-tab-pane label="威胁事件" name="threats">
          <div class="filters">
            <el-select
              v-model="threatTypeFilter"
              placeholder="类型"
              style="width: 220px"
              filterable
              clearable
              allow-create
              default-first-option
            >
              <el-option label="全部" value="" />
              <el-option v-for="t in threatTypeOptions" :key="t.value" :label="t.label" :value="t.value" />
            </el-select>

            <el-select v-model="threatSeverityFilter" placeholder="严重程度" style="width: 200px" clearable>
              <el-option label="全部" value="" />
              <el-option label="高风险(>=80)" value="high" />
              <el-option label="中风险(50-79)" value="medium" />
              <el-option label="低风险(<50)" value="low" />
            </el-select>

            <el-button type="primary" @click="onThreatFilter">筛选</el-button>
            <el-button @click="onThreatReset">重置</el-button>
          </div>

          <div class="table-wrap">
            <el-table :data="threatItems" v-loading="threatsLoading" style="width: 100%">
              <el-table-column prop="created_at" label="时间" width="180" />
              <el-table-column label="类型" width="170">
                <template #default="{ row }">
                  <el-tag effect="light" type="info">{{ row.threat_type || 'unknown' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="严重程度" width="120">
                <template #default="{ row }">
                  <el-tag :type="scoreMeta(row.score).type" effect="light">
                    {{ scoreMeta(row.score).label }} ({{ row.score ?? 0 }})
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column label="IP" width="150">
                <template #default="{ row }">
                  <el-link v-if="row.ip" type="primary" :underline="false" @click="jumpToIpRisk(row.ip)">
                    {{ row.ip }}
                  </el-link>
                  <span v-else>-</span>
                </template>
              </el-table-column>
              <el-table-column label="用户" width="120">
                <template #default="{ row }">
                  <el-link
                    v-if="row.user_id !== null && row.user_id !== undefined"
                    type="primary"
                    :underline="false"
                    @click="jumpToUserRisk(row.user_id)"
                  >
                    {{ row.user_id }}
                  </el-link>
                  <span v-else>-</span>
                </template>
              </el-table-column>
              <el-table-column label="操作路径" min-width="220">
                <template #default="{ row }">
                  <el-tooltip :content="pathText(row)" placement="top" :show-after="300">
                    <span class="mono ellipsis">{{ pathText(row) }}</span>
                  </el-tooltip>
                </template>
              </el-table-column>
              <el-table-column label="Payload预览" min-width="240">
                <template #default="{ row }">
                  <el-tooltip :content="payloadTooltip(row)" placement="top" :show-after="300">
                    <span class="ellipsis">{{ row.value_preview || '-' }}</span>
                  </el-tooltip>
                </template>
              </el-table-column>
            </el-table>
          </div>

          <div class="pagination">
            <el-pagination
              v-model:current-page="threatPage"
              :page-size="pageSize"
              :total="threatTotal"
              layout="prev, pager, next, jumper, ->, total"
              @current-change="loadThreats"
            />
            <div class="page-hint app-muted">第 {{ threatPage }} / {{ threatTotalPages }} 页</div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="封禁管理" name="bans">
          <div class="toolbar">
            <el-button type="primary" @click="openBanDialog()">手动封禁</el-button>
          </div>

          <el-tabs v-model="banTab" class="inner-tabs">
            <el-tab-pane label="IP黑名单" name="ips">
              <div class="table-wrap">
                <el-table :data="bannedIps" v-loading="bansLoading" style="width: 100%">
                  <el-table-column label="IP" width="180">
                    <template #default="{ row }">
                      <el-link type="primary" :underline="false" @click="jumpToIpRisk(row.ip)">
                        {{ row.ip || '-' }}
                      </el-link>
                    </template>
                  </el-table-column>
                  <el-table-column prop="reason" label="原因" min-width="260" />
                  <el-table-column label="过期时间" width="190">
                    <template #default="{ row }">{{ formatExpires(row.expires_at) }}</template>
                  </el-table-column>
                  <el-table-column label="操作" width="120" fixed="right">
                    <template #default="{ row }">
                      <el-button size="small" type="danger" plain @click="onUnbanIp(row.ip)">解除</el-button>
                    </template>
                  </el-table-column>
                </el-table>
              </div>
            </el-tab-pane>

            <el-tab-pane label="用户黑名单" name="users">
              <div class="table-wrap">
                <el-table :data="bannedUsers" v-loading="bansLoading" style="width: 100%">
                  <el-table-column label="用户ID" width="180">
                    <template #default="{ row }">
                      <el-link type="primary" :underline="false" @click="jumpToUserRisk(row.user_id)">
                        {{ row.user_id ?? '-' }}
                      </el-link>
                    </template>
                  </el-table-column>
                  <el-table-column prop="reason" label="原因" min-width="260" />
                  <el-table-column label="过期时间" width="190">
                    <template #default="{ row }">{{ formatExpires(row.expires_at) }}</template>
                  </el-table-column>
                  <el-table-column label="操作" width="120" fixed="right">
                    <template #default="{ row }">
                      <el-button size="small" type="danger" plain @click="onUnbanUser(row.user_id)">解除</el-button>
                    </template>
                  </el-table-column>
                </el-table>
              </div>
            </el-tab-pane>
          </el-tabs>
        </el-tab-pane>

        <el-tab-pane label="风险查询" name="risk">
          <el-tabs v-model="riskTab" class="inner-tabs">
            <el-tab-pane label="IP查询" name="ip">
              <div class="filters">
                <el-input v-model="riskIpInput" placeholder="输入IP，如 1.2.3.4" style="width: 260px" clearable />
                <el-button type="primary" :loading="riskLoading" @click="queryIpRisk">查询</el-button>
              </div>
            </el-tab-pane>
            <el-tab-pane label="用户查询" name="user">
              <div class="filters">
                <el-input v-model="riskUserIdInput" placeholder="输入用户ID，如 123" style="width: 260px" clearable />
                <el-button type="primary" :loading="riskLoading" @click="queryUserRisk">查询</el-button>
              </div>
            </el-tab-pane>
          </el-tabs>

          <el-card v-if="riskResult" shadow="never" :body-style="{ padding: '16px' }" class="sub-card">
            <div class="risk-head">
              <div class="risk-title">
                <strong v-if="riskResultKind === 'ip'">IP: {{ riskResult.ip }}</strong>
                <strong v-else>用户ID: {{ riskResult.user_id }}</strong>
                <span class="app-muted">风险分</span>
                <el-tag :type="scoreMeta(riskResult.risk_score).type" effect="light">
                  {{ riskResult.risk_score ?? 0 }}
                </el-tag>
                <el-tag v-if="riskResult.is_banned" type="danger" effect="light">已封禁</el-tag>
                <el-tag v-else type="success" effect="light">未封禁</el-tag>
              </div>
              <div class="toolbar">
                <el-button v-if="!riskResult.is_banned" type="primary" plain @click="openBanFromRisk">封禁</el-button>
                <el-button v-else type="danger" plain @click="unbanFromRisk">解除封禁</el-button>
                <el-button
                  v-if="riskResultKind === 'ip'"
                  type="warning"
                  plain
                  :loading="riskLoading"
                  @click="clearIpRiskScore"
                >
                  清除风险分
                </el-button>
              </div>
            </div>

            <div class="table-wrap">
              <el-table :data="riskResult.threat_history || []" v-loading="riskLoading" style="width: 100%">
                <el-table-column prop="created_at" label="时间" width="180" />
                <el-table-column label="类型" width="170">
                  <template #default="{ row }">
                    <el-tag effect="light" type="info">{{ row.threat_type || 'unknown' }}</el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="严重程度" width="120">
                  <template #default="{ row }">
                    <el-tag :type="scoreMeta(row.score).type" effect="light">
                      {{ scoreMeta(row.score).label }} ({{ row.score ?? 0 }})
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="操作路径" min-width="220">
                  <template #default="{ row }">
                    <el-tooltip :content="pathText(row)" placement="top" :show-after="300">
                      <span class="mono ellipsis">{{ pathText(row) }}</span>
                    </el-tooltip>
                  </template>
                </el-table-column>
                <el-table-column label="Payload预览" min-width="240">
                  <template #default="{ row }">
                    <el-tooltip :content="payloadTooltip(row)" placement="top" :show-after="300">
                      <span class="ellipsis">{{ row.value_preview || '-' }}</span>
                    </el-tooltip>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-card>
        </el-tab-pane>
      </el-tabs>
    </el-card>

    <el-dialog v-model="banDialogOpen" title="手动封禁" width="min(520px, 92vw)" @closed="resetBanForm">
      <el-form label-width="120px">
        <el-form-item label="类型">
          <el-radio-group v-model="banForm.kind">
            <el-radio-button label="ip">IP</el-radio-button>
            <el-radio-button label="user">用户</el-radio-button>
          </el-radio-group>
        </el-form-item>
        <el-form-item v-if="banForm.kind === 'ip'" label="IP">
          <el-input v-model="banForm.ip" placeholder="例如 1.2.3.4" />
        </el-form-item>
        <el-form-item v-else label="用户ID">
          <el-input v-model="banForm.user_id" placeholder="例如 123" />
        </el-form-item>
        <el-form-item label="原因">
          <el-input v-model="banForm.reason" type="textarea" :rows="3" placeholder="请输入封禁原因" />
        </el-form-item>
        <el-form-item label="永久封禁">
          <el-switch v-model="banForm.permanent" />
        </el-form-item>
        <el-form-item v-if="!banForm.permanent" label="持续(小时)">
          <el-input-number v-model="banForm.duration_hours" :min="1" :max="8760" />
        </el-form-item>
      </el-form>

      <template #footer>
        <div class="dialog-actions">
          <div class="spacer"></div>
          <el-button @click="banDialogOpen = false">取消</el-button>
          <el-button type="primary" :loading="banSubmitting" @click="submitBan">确认封禁</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<style scoped>
.page-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.toolbar {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}


.card {
  border-radius: var(--app-radius);
  border: 1px solid var(--app-border);
  background: var(--app-card-bg);
  box-shadow: var(--app-shadow-soft);
}

.sub-card {
  margin-top: 12px;
  border-radius: var(--app-radius);
  border: 1px solid var(--app-border);
}


.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 12px;
}

.table-wrap {
  overflow-x: auto;
  border-radius: 10px;
  border: 1px solid var(--app-border);
  background: #fff;
}

.ellipsis {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 14px;
  flex-wrap: wrap;
}

.page-hint {
  font-size: 12px;
}

.inner-tabs {
  margin-top: 6px;
}

.risk-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.risk-title {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.dialog-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.spacer {
  flex: 1;
}
</style>
