<template>
  <div class="credentials-manager">
    <div class="d-flex align-center flex-wrap ga-2 mb-3">
      <v-btn-toggle v-model="credTab" mandatory density="compact" variant="outlined">
        <v-btn size="small" value="passwords">密码预设</v-btn>
        <v-btn size="small" value="sshkeys">SSH 密钥</v-btn>
        <v-btn size="small" value="db">数据库凭据</v-btn>
      </v-btn-toggle>
      <v-spacer />
      <v-btn color="primary" variant="flat" size="small" prepend-icon="mdi-plus" @click="openAdd">添加</v-btn>
    </div>

    <template v-if="credTab === 'passwords'">
      <p class="text-body-2 text-medium-emphasis mb-2">
        保存常用 SSH 账号+密码模板；快速添加服务器时将按顺序轮询所有预设尝试登录。
      </p>
      <v-data-table
        :items="passwords"
        :headers="passwordHeaders"
        :loading="loading"
        hover
        density="comfortable"
        :items-per-page="20"
        :items-per-page-options="dataTablePageOptions"
      >
        <template #item.created_at="{ item }">
          <span class="text-caption text-medium-emphasis">{{ formatPresetTime(item.created_at) }}</span>
        </template>
        <template #item.actions="{ item }">
          <v-btn variant="text" size="x-small" density="compact" @click="editPassword(item)">编辑</v-btn>
          <v-btn variant="text" size="x-small" color="error" density="compact" @click="confirmDelete('password', item)">删除</v-btn>
        </template>
        <template #no-data>
          <div class="text-center text-medium-emphasis py-6">暂无密码预设</div>
        </template>
      </v-data-table>
    </template>

    <template v-else-if="credTab === 'sshkeys'">
      <p class="text-body-2 text-medium-emphasis mb-2">
        上传本地 PEM 私钥文件（如 id_rsa、.pem）；公钥由系统从私钥自动推导。
      </p>
      <v-data-table
        :items="sshKeys"
        :headers="sshKeyHeaders"
        :loading="loading"
        hover
        density="comfortable"
        :items-per-page="20"
        :items-per-page-options="dataTablePageOptions"
      >
        <template #item.private_key_set="{ item }">
          <v-chip :color="item.private_key_set ? 'success' : 'grey'" size="small" variant="tonal" label>
            {{ item.private_key_set ? '已设置' : '未设置' }}
          </v-chip>
        </template>
        <template #item.actions="{ item }">
          <v-btn variant="text" size="x-small" density="compact" @click="editSshKey(item)">编辑</v-btn>
          <v-btn variant="text" size="x-small" color="error" density="compact" @click="confirmDelete('sshkey', item)">删除</v-btn>
        </template>
        <template #no-data>
          <div class="text-center text-medium-emphasis py-6">暂无 SSH 密钥</div>
        </template>
      </v-data-table>
    </template>

    <template v-else>
      <v-data-table-server
        :items="dbCreds"
        :headers="dbCredHeaders"
        :items-length="totalItems"
        :loading="loading"
        :page="page"
        :items-per-page="itemsPerPage"
        :items-per-page-options="dataTablePageOptions"
        hover
        density="comfortable"
        @update:page="(p: number) => { page = p; loadDbCreds() }"
        @update:items-per-page="onItemsPerPageChange"
      >
        <template #item.actions="{ item }">
          <v-btn variant="text" size="x-small" density="compact" @click="editDbCred(item)">编辑</v-btn>
          <v-btn variant="text" size="x-small" color="error" density="compact" @click="confirmDelete('db', item)">删除</v-btn>
        </template>
        <template #no-data>
          <div class="text-center text-medium-emphasis py-6">暂无凭据</div>
        </template>
      </v-data-table-server>
    </template>

    <v-dialog v-model="showPasswordDialog" max-width="500">
      <v-card border>
        <v-card-title>{{ editingId ? '编辑密码预设' : '新建密码预设' }}</v-card-title>
        <v-card-text>
          <v-text-field
            v-model="pwForm.name"
            label="预设名称"
            variant="outlined"
            density="compact"
            class="mb-2"
            placeholder="如：生产环境 root 密码"
            :rules="[required('预设名称')]"
          />
          <v-text-field
            v-model="pwForm.username"
            label="SSH 用户名"
            variant="outlined"
            density="compact"
            class="mb-2"
            placeholder="root"
            :rules="[required('SSH 用户名')]"
          />
          <v-text-field
            v-model="pwForm.password"
            label="SSH 密码"
            variant="outlined"
            density="compact"
            type="password"
            :placeholder="editingId ? '留空表示不修改' : '必填'"
            :rules="editingId ? [] : [required('SSH 密码')]"
          />
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showPasswordDialog = false">取消</v-btn>
          <v-btn color="primary" variant="flat" @click="savePassword">保存</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <v-dialog v-model="showSshKeyDialog" max-width="500">
      <v-card border>
        <v-card-title>{{ editingId ? '编辑 SSH 密钥' : '新建 SSH 密钥' }}</v-card-title>
        <v-card-text>
          <v-text-field
            v-model="skForm.name"
            label="名称"
            variant="outlined"
            density="compact"
            class="mb-2"
            :rules="[required('名称')]"
          />
          <v-text-field
            v-model="skForm.username"
            label="SSH 用户名"
            variant="outlined"
            density="compact"
            class="mb-2"
            placeholder="root"
            :rules="[required('SSH 用户名')]"
          />
          <v-file-input
            v-model="skKeyFile"
            label="私钥文件"
            variant="outlined"
            density="compact"
            class="mb-2"
            prepend-icon="mdi-file-key"
            show-size
            :clearable="true"
            :hint="editingId ? '留空表示不更换私钥' : '选择 PEM 私钥文件（id_rsa、.pem、.key 等）'"
            persistent-hint
            @update:model-value="onSkKeyFileSelected"
          />
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showSshKeyDialog = false">取消</v-btn>
          <v-btn color="primary" variant="flat" @click="saveSshKey">保存</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <v-dialog v-model="showDbCredDialog" max-width="500">
      <v-card border>
        <v-card-title>{{ editingId ? '编辑数据库凭据' : '新建数据库凭据' }}</v-card-title>
        <v-card-text>
          <v-text-field v-model="dbForm.name" label="名称" variant="outlined" density="compact" class="mb-2" />
          <v-text-field
            v-model="dbForm.host"
            label="数据库主机"
            variant="outlined"
            density="compact"
            class="mb-2"
            hint="脚本 $DB_HOST；子机本机 MySQL 一般为 127.0.0.1"
            persistent-hint
          />
          <v-text-field v-model="dbForm.port" label="端口" variant="outlined" density="compact" type="number" class="mb-2" />
          <v-text-field v-model="dbForm.username" label="用户名" variant="outlined" density="compact" class="mb-2" />
          <v-text-field v-model="dbForm.password" label="密码" variant="outlined" density="compact" type="password" class="mb-2" />
          <v-text-field v-model="dbForm.database" label="数据库" variant="outlined" density="compact" />
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showDbCredDialog = false">取消</v-btn>
          <v-btn color="primary" variant="flat" @click="saveDbCred">保存</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <v-dialog v-model="showDeleteDialog" max-width="400">
      <v-card border>
        <v-card-title>确认删除</v-card-title>
        <v-card-text>确定要删除此凭据吗？</v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showDeleteDialog = false">取消</v-btn>
          <v-btn color="error" variant="flat" @click="doDelete">删除</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import { formatApiError } from '@/utils/apiError'
import { required } from '@/utils/validation'
import { DATA_TABLE_ITEMS_PER_PAGE_OPTIONS, headersWithoutSort } from '@/constants/dataTable'
import { fetchPagePerPage } from '@/utils/paginatedFetch'
import { formatDateTimeBeijing } from '@/utils/datetime'

defineOptions({ name: 'CredentialsManager' })

const props = defineProps<{
  /** 对话框打开时为 true，触发数据加载 */
  active?: boolean
}>()

const dataTablePageOptions = [...DATA_TABLE_ITEMS_PER_PAGE_OPTIONS]
const snackbar = useSnackbar()

interface PasswordItem { id: number; name: string; username?: string; created_at?: string }
interface SshKeyItem { id: number; name: string; username?: string; private_key_set?: boolean }
interface DbCredItem { id: number; name: string; host: string; port: number; username: string; database?: string }

const loading = ref(false)
const credTab = ref<'passwords' | 'sshkeys' | 'db'>('passwords')
const editingId = ref<number | null>(null)
const page = ref(1)
const totalItems = ref(0)
const itemsPerPage = ref(20)

const passwords = ref<PasswordItem[]>([])
const showPasswordDialog = ref(false)
const pwForm = ref({ name: '', username: 'root', password: '' })
const passwordHeaders = headersWithoutSort([
  { title: '预设名称', key: 'name' },
  { title: '用户名', key: 'username', width: 120 },
  { title: '创建时间', key: 'created_at', width: 180 },
  { title: '操作', key: 'actions', width: 150, align: 'end' as const },
])

const sshKeys = ref<SshKeyItem[]>([])
const showSshKeyDialog = ref(false)
const skForm = ref({ name: '', username: 'root' })
const skKeyFile = ref<File | File[] | null>(null)
const sshKeyHeaders = headersWithoutSort([
  { title: '名称', key: 'name' },
  { title: '用户名', key: 'username', width: 120 },
  { title: '私钥', key: 'private_key_set', width: 120 },
  { title: '操作', key: 'actions', width: 150, align: 'end' as const },
])

const dbCreds = ref<DbCredItem[]>([])
const showDbCredDialog = ref(false)
const DEFAULT_DB_HOST = '127.0.0.1'
const dbForm = ref({ name: '', host: DEFAULT_DB_HOST, port: 3306, username: '', password: '', database: '' })
const dbCredHeaders = headersWithoutSort([
  { title: '名称', key: 'name' },
  { title: '主机', key: 'host' },
  { title: '端口', key: 'port', width: 80 },
  { title: '用户名', key: 'username' },
  { title: '数据库', key: 'database' },
  { title: '操作', key: 'actions', width: 150, align: 'end' as const },
])

const showDeleteDialog = ref(false)
const deleteType = ref('')
const deleteId = ref<number | null>(null)

function onItemsPerPageChange(n: number) {
  itemsPerPage.value = n
  page.value = 1
  if (credTab.value === 'db') loadDbCreds()
}

function formatPresetTime(iso: string | undefined): string {
  return formatDateTimeBeijing(iso)
}

async function loadPasswords(silent = false) {
  if (!silent) loading.value = true
  try {
    const res = await http.getList<PasswordItem>('/presets/')
    passwords.value = res.items
    totalItems.value = res.total
  } catch (e: unknown) {
    passwords.value = []
    snackbar(formatApiError(e, '加载密码预设失败'), 'error')
  } finally {
    if (!silent) loading.value = false
  }
}

async function loadSshKeys(silent = false) {
  if (!silent) loading.value = true
  try {
    const res = await http.getList<SshKeyItem>('/ssh-key-presets/')
    sshKeys.value = res.items
    totalItems.value = res.total
  } catch (e: unknown) {
    sshKeys.value = []
    snackbar(formatApiError(e, '加载 SSH 密钥失败'), 'error')
  } finally {
    if (!silent) loading.value = false
  }
}

async function loadDbCreds(silent = false) {
  if (!silent) loading.value = true
  try {
    const res = await fetchPagePerPage<DbCredItem>('/scripts/credentials', {}, page.value, itemsPerPage.value)
    dbCreds.value = res.items
    totalItems.value = res.total
    if (itemsPerPage.value === -1) page.value = 1
  } catch (e: unknown) {
    dbCreds.value = []
    snackbar(formatApiError(e, '加载数据库凭据失败'), 'error')
  } finally {
    if (!silent) loading.value = false
  }
}

function loadCurrentTab(silent = false) {
  switch (credTab.value) {
    case 'passwords': void loadPasswords(silent); break
    case 'sshkeys': void loadSshKeys(silent); break
    case 'db': void loadDbCreds(silent); break
  }
}

watch(credTab, () => {
  page.value = 1
  loadCurrentTab()
})

watch(
  () => props.active,
  (active) => {
    if (active) loadCurrentTab()
  },
  { immediate: true },
)

function editPassword(item: PasswordItem) {
  editingId.value = item.id
  pwForm.value = { name: item.name, username: item.username || 'root', password: '' }
  showPasswordDialog.value = true
}

async function savePassword() {
  const name = pwForm.value.name.trim()
  const password = pwForm.value.password
  if (!name) {
    snackbar('请填写预设名称', 'error')
    return
  }
  if (!editingId.value && !password) {
    snackbar('请填写 SSH 密码', 'error')
    return
  }
  try {
    const username = pwForm.value.username.trim() || 'root'
    if (editingId.value) {
      const body: { name: string; username: string; encrypted_pw?: string } = { name, username }
      if (password) body.encrypted_pw = password
      await http.put(`/presets/${editingId.value}`, body)
    } else {
      await http.post('/presets/', { name, username, encrypted_pw: password })
    }
    showPasswordDialog.value = false
    loadPasswords()
    snackbar('保存成功')
  } catch (e: unknown) {
    snackbar(formatApiError(e, '保存失败'), 'error')
  }
}

function pickSkKeyFile(files: File | File[] | null): File | null {
  if (!files) return null
  if (Array.isArray(files)) return files[0] ?? null
  return files
}

function onSkKeyFileSelected(files: File | File[] | null) {
  if (editingId.value || skForm.value.name.trim()) return
  const file = pickSkKeyFile(files)
  if (!file?.name) return
  const base = file.name.replace(/\.(pem|key|ppk)$/i, '').trim()
  if (base) skForm.value.name = base
}

async function readSkPrivateKeyFromFile(): Promise<string> {
  const file = pickSkKeyFile(skKeyFile.value)
  if (!file) return ''
  const text = (await file.text()).trim()
  if (!text.includes('PRIVATE KEY')) {
    throw new Error('不是有效的 PEM 私钥文件')
  }
  return text
}

function resetSshKeyDialog() {
  skForm.value = { name: '', username: 'root' }
  skKeyFile.value = null
}

function editSshKey(item: SshKeyItem) {
  editingId.value = item.id
  skForm.value = { name: item.name, username: item.username || 'root' }
  skKeyFile.value = null
  showSshKeyDialog.value = true
}

async function saveSshKey() {
  const name = skForm.value.name.trim()
  const username = skForm.value.username.trim() || 'root'
  if (!name) {
    snackbar('请填写名称', 'error')
    return
  }
  let privateKey = ''
  try {
    privateKey = await readSkPrivateKeyFromFile()
  } catch (e: unknown) {
    snackbar(e instanceof Error ? e.message : '读取私钥文件失败', 'error')
    return
  }
  if (!editingId.value && !privateKey) {
    snackbar('请选择私钥文件', 'error')
    return
  }
  try {
    if (editingId.value) {
      const body: { name: string; username: string; private_key?: string } = { name, username }
      if (privateKey) body.private_key = privateKey
      await http.put(`/ssh-key-presets/${editingId.value}`, body)
    } else {
      await http.post('/ssh-key-presets/', { name, username, private_key: privateKey })
    }
    showSshKeyDialog.value = false
    loadSshKeys()
    snackbar('保存成功')
  } catch (e: unknown) {
    snackbar(formatApiError(e, '保存失败'), 'error')
  }
}

function editDbCred(item: DbCredItem) {
  editingId.value = item.id
  dbForm.value = {
    name: item.name,
    host: item.host || DEFAULT_DB_HOST,
    port: item.port || 3306,
    username: item.username || '',
    password: '',
    database: item.database || '',
  }
  showDbCredDialog.value = true
}

async function saveDbCred() {
  try {
    if (editingId.value) await http.put(`/scripts/credentials/${editingId.value}`, dbForm.value)
    else await http.post('/scripts/credentials', dbForm.value)
    showDbCredDialog.value = false
    loadDbCreds()
    snackbar('保存成功')
  } catch (e: unknown) {
    snackbar(formatApiError(e, '保存失败'), 'error')
  }
}

function openAdd() {
  editingId.value = null
  if (credTab.value === 'passwords') {
    pwForm.value = { name: '', username: 'root', password: '' }
    showPasswordDialog.value = true
  } else if (credTab.value === 'sshkeys') {
    resetSshKeyDialog()
    showSshKeyDialog.value = true
  } else {
    dbForm.value = { name: '', host: DEFAULT_DB_HOST, port: 3306, username: '', password: '', database: '' }
    showDbCredDialog.value = true
  }
}

function confirmDelete(type: string, item: { id: number }) {
  deleteType.value = type
  deleteId.value = item.id
  showDeleteDialog.value = true
}

async function doDelete() {
  try {
    if (deleteType.value === 'password') await http.delete(`/presets/${deleteId.value}`)
    else if (deleteType.value === 'sshkey') await http.delete(`/ssh-key-presets/${deleteId.value}`)
    else await http.delete(`/scripts/credentials/${deleteId.value}`)
    showDeleteDialog.value = false
    loadCurrentTab()
    snackbar('已删除')
  } catch (e: unknown) {
    snackbar(formatApiError(e, '删除失败'), 'error')
  }
}
</script>
