<script lang="ts" setup>
/**
 * 队列任务监控大屏：实时快照 + 趋势 + 失败 TOP / 最近失败
 * 菜单 name 须与 xk_menu.name=QueueJobMonitor 一致
 */
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';

import { AnalysisChartCard, Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';

import {
  Button,
  Card,
  Radio,
  Spin,
  Table,
  Tag,
  message,
} from 'ant-design-vue';

import { getQueueJobStats, type QueueJobMonitorResult } from './api';
import CategoryPieChart from './components/CategoryPieChart.vue';
import ProbeModal from './components/ProbeModal.vue';
import StatusTrendChart from './components/StatusTrendChart.vue';

defineOptions({ name: 'QueueJobMonitor' });

const router = useRouter();
const userStore = useUserStore();
/** 仅超管 role_id=1 可见测试按钮，与后端 SUPPER_ADMIN 一致 */
const isSuperAdmin = computed(() => {
  const roleId = Number(
    userStore.userInfo?.role_id ?? userStore.userInfo?.roles?.id,
  );
  return roleId === 1;
});

const loading = ref(false);
const hours = ref(24);
const data = ref<QueueJobMonitorResult | null>(null);
let timer: ReturnType<typeof setInterval> | null = null;

const [ProbeModalComp, probeModalApi] = useVbenModal({
  connectedComponent: ProbeModal,
});

function openProbeModal() {
  probeModalApi.open();
}

const summaryCards = computed(() => {
  const s = data.value?.summary;
  if (!s) return [];
  return [
    {
      key: 'total',
      title: '总任务',
      value: s.total,
      tip: '未删除全部记录',
      color: '#1677ff',
    },
    {
      key: 'pending',
      title: '排队中',
      value: s.pending,
      tip: '已就绪待消费',
      color: '#faad14',
    },
    {
      key: 'delayed',
      title: '延迟队列',
      value: s.delayed,
      tip: 'available_at > 当前时间',
      color: '#722ed1',
    },
    {
      key: 'running',
      title: '正在执行',
      value: s.running,
      tip: 'status=1',
      color: '#13c2c2',
    },
    {
      key: 'success',
      title: '已完成',
      value: s.success,
      tip: 'status=2',
      color: '#52c41a',
    },
    {
      key: 'failed',
      title: '失败',
      value: s.failed,
      tip:
        s.fail_rate == null ? 'status=3' : `失败率 ${s.fail_rate}%`,
      color: '#ff4d4f',
    },
  ];
});

const failColumns = [
  { title: 'ID', dataIndex: 'id', width: 80 },
  { title: '标题', dataIndex: 'title', ellipsis: true },
  { title: '任务类型', dataIndex: 'job_name_txt', width: 140 },
  { title: '分类', dataIndex: 'category_txt', width: 100 },
  { title: '错误摘要', dataIndex: 'error_message', ellipsis: true },
  { title: '时间', dataIndex: 'updated_at', width: 170 },
];

/**
 * 拉取统计；静默刷新时不打断 UI
 */
async function loadStats(silent = false) {
  if (!silent) loading.value = true;
  try {
    const res = await getQueueJobStats(hours.value);
    data.value = (res?.data || res) as QueueJobMonitorResult;
  } catch {
    if (!silent) message.error('加载监控数据失败');
  } finally {
    if (!silent) loading.value = false;
  }
}

function goBusinessFailed() {
  router.push({ path: '/log/queue-job/business', query: { status: '3' } });
}

function goInfraFailed() {
  router.push({ path: '/log/queue-job/infra', query: { status: '3' } });
}

onMounted(() => {
  loadStats();
  // 大屏自动刷新，减轻手工点刷新
  timer = setInterval(() => loadStats(true), 30_000);
});

onUnmounted(() => {
  if (timer) clearInterval(timer);
  timer = null;
});
</script>

<template>
  <Page auto-content-height title="队列任务监控">
    <div class="mb-4 flex flex-wrap items-center justify-between gap-3">
      <div class="text-sm text-gray-500">
        实时快照 · 每 30 秒自动刷新
        <span v-if="data?.now" class="ml-2">
          （{{ new Date(Number(data.now) * 1000).toLocaleString() }}）
        </span>
      </div>
      <div class="flex items-center gap-2">
        <Radio.Group
          v-model:value="hours"
          button-style="solid"
          option-type="button"
          :options="[
            { label: '近24小时', value: 24 },
            { label: '近7天', value: 168 },
          ]"
          @change="loadStats()"
        />
        <Button
          v-if="isSuperAdmin"
          type="default"
          danger
          @click="openProbeModal"
        >
          测试任务
        </Button>
        <Button type="primary" :loading="loading" @click="loadStats()">
          刷新
        </Button>
      </div>
    </div>

    <ProbeModalComp @success="loadStats()" />

    <Spin :spinning="loading">
      <div class="monitor-stat-grid mb-4">
        <Card
          v-for="card in summaryCards"
          :key="card.key"
          class="monitor-stat-card"
          :bordered="true"
        >
          <div class="text-sm text-gray-500">{{ card.title }}</div>
          <div class="monitor-stat-value" :style="{ color: card.color }">
            {{ Number(card.value || 0).toLocaleString() }}
          </div>
          <div class="mt-2 text-xs text-gray-400">{{ card.tip }}</div>
        </Card>
      </div>

      <div class="mb-4 grid grid-cols-1 gap-4 xl:grid-cols-3">
        <AnalysisChartCard class="xl:col-span-2" title="任务趋势">
          <StatusTrendChart :data="data?.trend || []" />
        </AnalysisChartCard>
        <AnalysisChartCard title="分类占比">
          <CategoryPieChart :data="data?.by_category || []" />
        </AnalysisChartCard>
      </div>

      <div class="mb-4 grid grid-cols-1 gap-4 xl:grid-cols-2">
        <Card title="失败 TOP（窗口内）" :bordered="true">
          <div
            v-if="!(data?.top_failed_jobs || []).length"
            class="py-8 text-center text-gray-400"
          >
            暂无失败
          </div>
          <div
            v-for="item in data?.top_failed_jobs || []"
            :key="item.job_name"
            class="mb-3 flex items-center justify-between gap-2"
          >
            <span class="truncate">{{ item.label }}</span>
            <Tag color="error">{{ item.count }}</Tag>
          </div>
          <div class="mt-2 flex gap-2">
            <Button size="small" @click="goBusinessFailed">业务失败列表</Button>
            <Button size="small" @click="goInfraFailed">基础设施失败列表</Button>
          </div>
        </Card>
        <Card title="最近失败" :bordered="true">
          <Table
            size="small"
            :pagination="false"
            :columns="failColumns"
            :data-source="data?.recent_failures || []"
            :scroll="{ x: 720 }"
            row-key="id"
          />
        </Card>
      </div>
    </Spin>
  </Page>
</template>

<style scoped>
.monitor-stat-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 12px;
}

@media (min-width: 768px) {
  .monitor-stat-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 1280px) {
  .monitor-stat-grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

.monitor-stat-card :deep(.ant-card-body) {
  padding: 16px 18px;
}

.monitor-stat-value {
  margin-top: 6px;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.15;
}
</style>
