<template>
  <div class="scheduler-page">
    <div class="scheduler-header">
      <div>
        <h2 class="ops-page-title">调度中心</h2>
        <div class="scheduler-header__meta">系统周期任务启停、配置、手动触发与运行追踪</div>
      </div>
      <a-space>
        <a-button :loading="instancesLoading" @click="loadInstances">
          <template #icon><ClusterOutlined /></template>
          实例
        </a-button>
        <a-button type="primary" :loading="loading" @click="reload">
          <template #icon><ReloadOutlined /></template>
          刷新
        </a-button>
      </a-space>
    </div>

    <div class="scheduler-summary">
      <div class="summary-tile">
        <span>任务总数</span>
        <strong>{{ total }}</strong>
      </div>
      <div class="summary-tile summary-tile--green">
        <span>已启用</span>
        <strong>{{ enabledCount }}</strong>
      </div>
      <div class="summary-tile summary-tile--gold">
        <span>运行中</span>
        <strong>{{ runningCount }}</strong>
      </div>
      <div class="summary-tile summary-tile--red">
        <span>最近失败</span>
        <strong>{{ failedCount }}</strong>
      </div>
    </div>

    <div class="ops-card">
      <div class="ops-toolbar scheduler-toolbar">
        <a-input
          v-model:value="filter.keyword"
          placeholder="搜索任务名 / key"
          allow-clear
          style="width: 260px"
          @press-enter="resetAndReload"
        />
        <a-select
          v-model:value="filter.category"
          :options="categoryOptions"
          allow-clear
          placeholder="分类"
          style="width: 160px"
          @change="resetAndReload"
        />
        <a-select
          v-model:value="filter.enabled"
          :options="enabledOptions"
          allow-clear
          placeholder="启用状态"
          style="width: 140px"
          @change="resetAndReload"
        />
        <a-button @click="resetAndReload">查询</a-button>
      </div>

      <a-table
        :columns="columns"
        :data-source="rows"
        :loading="loading"
        :pagination="pagination"
        row-key="job_key"
        :scroll="{ x: 1320 }"
        @change="onTableChange"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'job'">
            <div class="job-cell">
              <div class="job-title">
                <span>{{ record.display_name }}</span>
                <a-tag :color="categoryColor(record.category)">{{ categoryLabel(record.category) }}</a-tag>
              </div>
              <code>{{ record.job_key }}</code>
              <div class="muted">{{ record.description || '—' }}</div>
            </div>
          </template>
          <template v-else-if="column.key === 'enabled'">
            <a-switch
              :checked="record.enabled"
              :loading="savingKey === record.job_key"
              checked-children="启用"
              un-checked-children="暂停"
              @change="(checked: boolean) => toggleJob(record, checked)"
            />
          </template>
          <template v-else-if="column.key === 'schedule'">
            <div>{{ scheduleText(record) }}</div>
            <div class="muted">超时 {{ record.timeout_seconds }}s</div>
          </template>
          <template v-else-if="column.key === 'runtime'">
            <a-tag v-if="record.running_run" color="processing">运行中</a-tag>
            <a-tag v-else :color="runStatusColor(record.last_run?.status)">
              {{ runStatusLabel(record.last_run?.status) }}
            </a-tag>
            <div class="muted">
              {{ record.last_run ? formatDate(record.last_run.started_at) : '暂无运行记录' }}
            </div>
          </template>
          <template v-else-if="column.key === 'stats'">
            <div class="mono-line">{{ statsSummary(record.last_run?.stats) }}</div>
            <div v-if="record.pending_triggers" class="muted">待触发 {{ record.pending_triggers }}</div>
          </template>
          <template v-else-if="column.key === 'actions'">
            <div class="table-actions-row">
              <a-tooltip title="编辑配置">
                <a-button type="text" class="action-btn action-edit" @click="openEdit(record)">
                  <SettingOutlined />
                </a-button>
              </a-tooltip>
              <a-tooltip title="立即运行">
                <a-popconfirm title="确认立即运行该调度任务？" ok-text="运行" cancel-text="取消" @confirm="triggerJob(record, 'run')">
                  <a-button type="text" class="action-btn action-play" :loading="triggeringKey === `${record.job_key}:run`">
                    <PlayCircleOutlined />
                  </a-button>
                </a-popconfirm>
              </a-tooltip>
              <a-tooltip title="Dry-run">
                <a-button type="text" class="action-btn action-warn" :loading="triggeringKey === `${record.job_key}:dry-run`" @click="triggerJob(record, 'dry-run')">
                  <ExperimentOutlined />
                </a-button>
              </a-tooltip>
              <a-tooltip title="运行记录">
                <a-button type="text" class="action-btn" @click="openRuns(record)">
                  <HistoryOutlined />
                </a-button>
              </a-tooltip>
            </div>
          </template>
        </template>
      </a-table>
    </div>

    <a-drawer v-model:open="editOpen" width="620" :title="editTitle" :destroy-on-close="true">
      <a-form v-if="editing" layout="vertical">
        <a-form-item label="启用状态">
          <a-switch v-model:checked="editForm.enabled" checked-children="启用" un-checked-children="暂停" />
        </a-form-item>
        <a-form-item label="调度类型">
          <a-segmented v-model:value="editForm.schedule_type" :options="scheduleTypeOptions" />
        </a-form-item>
        <a-form-item v-if="editForm.schedule_type === 'cron'" label="Cron 表达式">
          <a-input v-model:value="editForm.cron_expr" placeholder="例如 0 30 2 * * *" />
        </a-form-item>
        <a-form-item label="间隔秒数">
          <a-input-number v-model:value="editForm.interval_seconds" :min="1" :max="86400" style="width: 100%" />
        </a-form-item>
        <a-form-item label="超时秒数">
          <a-input-number v-model:value="editForm.timeout_seconds" :min="1" :max="3600" style="width: 100%" />
        </a-form-item>
        <a-form-item label="批大小">
          <a-input-number v-model:value="editForm.batch_size" :min="1" :max="100000" style="width: 100%" />
        </a-form-item>
        <a-form-item label="时区">
          <a-input v-model:value="editForm.timezone" />
        </a-form-item>
        <a-form-item label="高级配置 JSON">
          <a-textarea v-model:value="editConfigText" :rows="12" spellcheck="false" class="config-editor" />
        </a-form-item>
        <div class="drawer-actions">
          <a-button @click="editOpen = false">取消</a-button>
          <a-button type="primary" :loading="savingKey === editing.job_key" @click="saveEdit">保存</a-button>
        </div>
      </a-form>
    </a-drawer>

    <a-drawer v-model:open="runsOpen" width="820" :title="runsTitle" :destroy-on-close="true">
      <a-table
        :columns="runColumns"
        :data-source="runRows"
        :loading="runsLoading"
        :pagination="runPagination"
        row-key="id"
        :scroll="{ x: 900 }"
        @change="onRunTableChange"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'status'">
            <a-tag :color="runStatusColor(record.status)">{{ runStatusLabel(record.status) }}</a-tag>
            <div class="muted">{{ record.trigger_type }}</div>
          </template>
          <template v-else-if="column.key === 'time'">
            <div>{{ formatDate(record.started_at) }}</div>
            <div class="muted">{{ formatMs(record.duration_ms) }}</div>
          </template>
          <template v-else-if="column.key === 'stats'">
            <pre class="run-json">{{ formatJSON(record.stats) }}</pre>
          </template>
          <template v-else-if="column.key === 'error'">
            <span class="error-text">{{ record.error_message || '—' }}</span>
          </template>
        </template>
      </a-table>
    </a-drawer>

    <a-drawer v-model:open="instancesOpen" width="720" title="Scheduler 实例" :destroy-on-close="true">
      <a-table :columns="instanceColumns" :data-source="instances" :loading="instancesLoading" row-key="instance_id" :pagination="false">
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'instance'">
            <div class="mono-line">{{ record.instance_id }}</div>
            <div class="muted">{{ record.hostname }} · {{ record.process }}</div>
          </template>
          <template v-else-if="column.key === 'seen'">
            <div>{{ formatDate(record.last_seen_at) }}</div>
            <div class="muted">启动 {{ formatDate(record.started_at) }}</div>
          </template>
        </template>
      </a-table>
    </a-drawer>
  </div>
</template>

<script setup lang="ts">
import {
  ClusterOutlined,
  ExperimentOutlined,
  HistoryOutlined,
  PlayCircleOutlined,
  ReloadOutlined,
  SettingOutlined,
} from '@ant-design/icons-vue'
import type { TablePaginationConfig } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import { computed, onMounted, reactive, ref } from 'vue'

import { showOpsError } from '@/lib/errors'
import { http } from '@/lib/http'

interface SchedulerRun {
  id: number
  job_key: string
  trigger_type: string
  status: string
  config_version: number
  stats: Record<string, unknown>
  error_message?: string | null
  started_at: string
  finished_at?: string | null
  duration_ms?: number | null
}

interface SchedulerJob {
  job_key: string
  display_name: string
  category: string
  description?: string | null
  enabled: boolean
  schedule_type: string
  interval_seconds: number
  cron_expr?: string | null
  timezone: string
  timeout_seconds: number
  batch_size?: number | null
  max_concurrency: number
  config: Record<string, unknown>
  version: number
  updated_at: string
  last_run?: SchedulerRun | null
  running_run?: SchedulerRun | null
  pending_triggers: number
}

interface SchedulerInstance {
  instance_id: string
  hostname: string
  process: string
  build_version?: string | null
  started_at: string
  last_seen_at: string
  metadata: Record<string, unknown>
}

interface ListResult<T> {
  items: T[]
  total: number
  page: number
  size: number
}

const categoryOptions = [
  { label: '监控', value: 'monitoring' },
  { label: '内容', value: 'content' },
  { label: '知识库', value: 'knowledge' },
  { label: '生成', value: 'generation' },
]

const enabledOptions = [
  { label: '启用', value: 'true' },
  { label: '暂停', value: 'false' },
]

const scheduleTypeOptions = [
  { label: '间隔', value: 'interval' },
  { label: 'Cron', value: 'cron' },
  { label: '手动', value: 'manual' },
]

const columns = [
  { title: '任务', key: 'job', width: 360, fixed: 'left' },
  { title: '开关', key: 'enabled', width: 120 },
  { title: '调度', key: 'schedule', width: 180 },
  { title: '运行态', key: 'runtime', width: 190 },
  { title: '最近结果', key: 'stats', width: 280 },
  { title: '操作', key: 'actions', width: 172, fixed: 'right' },
]

const runColumns = [
  { title: '状态', key: 'status', width: 130 },
  { title: '时间', key: 'time', width: 190 },
  { title: '统计', key: 'stats', width: 360 },
  { title: '错误', key: 'error', width: 220 },
]

const instanceColumns = [
  { title: '实例', key: 'instance' },
  { title: '心跳', key: 'seen', width: 220 },
]

const filter = reactive({
  keyword: '',
  category: undefined as string | undefined,
  enabled: undefined as string | undefined,
})
const rows = ref<SchedulerJob[]>([])
const total = ref(0)
const page = ref(1)
const size = ref(50)
const loading = ref(false)
const savingKey = ref('')
const triggeringKey = ref('')
const editOpen = ref(false)
const editing = ref<SchedulerJob | null>(null)
const editConfigText = ref('')
const editForm = reactive({
  enabled: true,
  schedule_type: 'interval',
  interval_seconds: 300,
  cron_expr: '',
  timeout_seconds: 30,
  batch_size: null as number | null,
  timezone: 'Asia/Shanghai',
})
const runsOpen = ref(false)
const runsJob = ref<SchedulerJob | null>(null)
const runRows = ref<SchedulerRun[]>([])
const runsLoading = ref(false)
const runPage = ref(1)
const runSize = ref(20)
const runTotal = ref(0)
const instancesOpen = ref(false)
const instances = ref<SchedulerInstance[]>([])
const instancesLoading = ref(false)

const pagination = computed<TablePaginationConfig>(() => ({
  current: page.value,
  pageSize: size.value,
  total: total.value,
  showSizeChanger: true,
  pageSizeOptions: ['20', '50', '100', '200'],
}))

const runPagination = computed<TablePaginationConfig>(() => ({
  current: runPage.value,
  pageSize: runSize.value,
  total: runTotal.value,
  showSizeChanger: true,
}))

const enabledCount = computed(() => rows.value.filter((row) => row.enabled).length)
const runningCount = computed(() => rows.value.filter((row) => row.running_run).length)
const failedCount = computed(() => rows.value.filter((row) => row.last_run?.status === 'failed').length)
const editTitle = computed(() => (editing.value ? `编辑 · ${editing.value.display_name}` : '编辑调度任务'))
const runsTitle = computed(() => (runsJob.value ? `运行记录 · ${runsJob.value.display_name}` : '运行记录'))

async function reload() {
  loading.value = true
  try {
    const params: Record<string, unknown> = { page: page.value, size: size.value }
    if (filter.keyword.trim()) params.keyword = filter.keyword.trim()
    if (filter.category) params.category = filter.category
    if (filter.enabled) params.enabled = filter.enabled
    const result = await http.get<ListResult<SchedulerJob>>('/scheduler/jobs', params)
    rows.value = result.items
    total.value = result.total
  } catch (error) {
    showOpsError(error)
  } finally {
    loading.value = false
  }
}

function resetAndReload() {
  page.value = 1
  void reload()
}

function onTableChange(pag: TablePaginationConfig) {
  page.value = pag.current ?? 1
  size.value = pag.pageSize ?? 50
  void reload()
}

async function toggleJob(row: SchedulerJob, enabled: boolean) {
  savingKey.value = row.job_key
  try {
    const action = enabled ? 'enable' : 'disable'
    const updated = await http.post<SchedulerJob>(`/scheduler/jobs/${row.job_key}/${action}`)
    patchJob(updated)
    message.success(enabled ? '已启用' : '已暂停')
  } catch (error) {
    showOpsError(error)
  } finally {
    savingKey.value = ''
  }
}

function openEdit(row: SchedulerJob) {
  editing.value = row
  editForm.enabled = row.enabled
  editForm.schedule_type = row.schedule_type
  editForm.interval_seconds = row.interval_seconds
  editForm.cron_expr = row.cron_expr ?? ''
  editForm.timeout_seconds = row.timeout_seconds
  editForm.batch_size = row.batch_size ?? null
  editForm.timezone = row.timezone
  editConfigText.value = JSON.stringify(row.config ?? {}, null, 2)
  editOpen.value = true
}

async function saveEdit() {
  if (!editing.value) return
  let config: Record<string, unknown>
  try {
    config = JSON.parse(editConfigText.value || '{}') as Record<string, unknown>
  } catch {
    message.warning('高级配置必须是合法 JSON')
    return
  }
  savingKey.value = editing.value.job_key
  try {
    const updated = await http.patch<SchedulerJob>(`/scheduler/jobs/${editing.value.job_key}`, {
      enabled: editForm.enabled,
      schedule_type: editForm.schedule_type,
      interval_seconds: editForm.interval_seconds,
      cron_expr: editForm.cron_expr,
      timeout_seconds: editForm.timeout_seconds,
      batch_size: editForm.batch_size,
      timezone: editForm.timezone,
      config,
    })
    patchJob(updated)
    editOpen.value = false
    message.success('已保存')
  } catch (error) {
    showOpsError(error)
  } finally {
    savingKey.value = ''
  }
}

async function triggerJob(row: SchedulerJob, action: 'run' | 'dry-run') {
  triggeringKey.value = `${row.job_key}:${action}`
  try {
    await http.post(`/scheduler/jobs/${row.job_key}/${action}`, {
      reason: action === 'dry-run' ? 'ops console dry-run' : 'ops console manual run',
    })
    message.success(action === 'dry-run' ? '已提交 dry-run' : '已提交运行')
    await reload()
  } catch (error) {
    showOpsError(error)
  } finally {
    triggeringKey.value = ''
  }
}

function openRuns(row: SchedulerJob) {
  runsJob.value = row
  runPage.value = 1
  runsOpen.value = true
  void loadRuns()
}

async function loadRuns() {
  if (!runsJob.value) return
  runsLoading.value = true
  try {
    const result = await http.get<ListResult<SchedulerRun>>(`/scheduler/jobs/${runsJob.value.job_key}/runs`, {
      page: runPage.value,
      size: runSize.value,
    })
    runRows.value = result.items
    runTotal.value = result.total
  } catch (error) {
    showOpsError(error)
  } finally {
    runsLoading.value = false
  }
}

function onRunTableChange(pag: TablePaginationConfig) {
  runPage.value = pag.current ?? 1
  runSize.value = pag.pageSize ?? 20
  void loadRuns()
}

async function loadInstances() {
  instancesOpen.value = true
  instancesLoading.value = true
  try {
    const result = await http.get<{ items: SchedulerInstance[] }>('/scheduler/instances')
    instances.value = result.items
  } catch (error) {
    showOpsError(error)
  } finally {
    instancesLoading.value = false
  }
}

function patchJob(item: SchedulerJob) {
  const index = rows.value.findIndex((row) => row.job_key === item.job_key)
  if (index >= 0) rows.value[index] = { ...rows.value[index], ...item }
}

function scheduleText(row: SchedulerJob): string {
  const runAt = row.config?.run_at_local
  if (typeof runAt === 'string' && runAt) return `每日 ${runAt}`
  if (row.schedule_type === 'manual') return '手动'
  if (row.schedule_type === 'cron') return row.cron_expr || 'Cron'
  return `每 ${formatSeconds(row.interval_seconds)}`
}

function categoryLabel(value: string): string {
  return categoryOptions.find((item) => item.value === value)?.label ?? value
}

function categoryColor(value: string): string {
  if (value === 'monitoring') return 'blue'
  if (value === 'generation') return 'purple'
  if (value === 'knowledge') return 'green'
  if (value === 'content') return 'geekblue'
  return 'default'
}

function runStatusLabel(value?: string | null): string {
  if (!value) return '未运行'
  const labels: Record<string, string> = {
    running: '运行中',
    success: '成功',
    failed: '失败',
    skipped: '跳过',
  }
  return labels[value] ?? value
}

function runStatusColor(value?: string | null): string {
  if (value === 'success') return 'green'
  if (value === 'failed') return 'red'
  if (value === 'running') return 'processing'
  if (value === 'skipped') return 'default'
  return 'default'
}

function statsSummary(stats?: Record<string, unknown> | null): string {
  if (!stats) return '—'
  const pairs = Object.entries(stats)
    .filter(([key]) => ['deleted_count', 'candidate_count', 'created_task_count', 'claimed_count', 'expired_task_count', 'anomaly_count'].includes(key))
    .map(([key, value]) => `${key}=${String(value)}`)
  return pairs.length ? pairs.join(' · ') : '—'
}

function formatDate(value?: string | null): string {
  if (!value) return '—'
  return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
}

function formatSeconds(seconds: number): string {
  if (seconds < 60) return `${seconds}s`
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m`
  return `${Math.floor(seconds / 3600)}h`
}

function formatMs(value?: number | null): string {
  if (value == null) return '—'
  if (value < 1000) return `${value}ms`
  return `${Math.round(value / 100) / 10}s`
}

function formatJSON(value: unknown): string {
  try {
    return JSON.stringify(value ?? {}, null, 2)
  } catch {
    return String(value)
  }
}

onMounted(() => {
  void reload()
})
</script>

<style scoped>
.scheduler-page {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.scheduler-header {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;
}

.scheduler-header__meta,
.muted {
  color: #64748b;
  font-size: 13px;
}

.scheduler-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.summary-tile {
  border: 1px solid #dbe4ef;
  border-radius: 6px;
  background: #fff;
  padding: 16px 18px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.summary-tile strong {
  font-size: 26px;
  line-height: 1;
  color: #0f172a;
}

.summary-tile--green strong {
  color: #15803d;
}

.summary-tile--gold strong {
  color: #b45309;
}

.summary-tile--red strong {
  color: #b91c1c;
}

.scheduler-toolbar {
  flex-wrap: wrap;
}

.job-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.job-title {
  display: flex;
  gap: 8px;
  align-items: center;
  font-weight: 700;
  color: #0f172a;
}

.mono-line,
.job-cell code {
  font-family: 'SFMono-Regular', Consolas, monospace;
  font-size: 12px;
  color: #334155;
}

.table-actions-row {
  display: flex;
  gap: 4px;
}

.config-editor,
.run-json {
  font-family: 'SFMono-Regular', Consolas, monospace;
  font-size: 12px;
}

.run-json {
  max-height: 140px;
  overflow: auto;
  margin: 0;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 6px;
  padding: 8px;
}

.drawer-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 18px;
}

.error-text {
  color: #b91c1c;
}

@media (max-width: 900px) {
  .scheduler-header {
    flex-direction: column;
  }

  .scheduler-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
</style>
