<script setup lang="ts">
import { ElMessage } from "element-plus";
import { LineChart } from "echarts/charts";
import { DataZoomComponent, GridComponent, LegendComponent, MarkPointComponent, TooltipComponent } from "echarts/components";
import { init, use, type ECharts } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { nextTick, onBeforeUnmount, onMounted, reactive, ref } from "vue";

import { api } from "../services/api";
import type { DashboardStats, PeakTrafficResult, TrafficGrain } from "../types/api";

use([LineChart, GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, MarkPointComponent, CanvasRenderer]);

const stats = ref<DashboardStats | null>(null);
const traffic = ref<PeakTrafficResult | null>(null);
const trafficChart = ref<HTMLElement | null>(null);
let trafficChartInstance: ECharts | null = null;
const trafficGrain = ref<TrafficGrain>("hour");
const storageStats = ref<Record<string, any> | null>(null);
const storageLoading = ref(false);
const trafficLoading = ref(false);
const loading = ref(false);
const filters = reactive({ start: "", end: "" });

const trafficOptions: Array<{ label: string; value: TrafficGrain }> = [
  { label: "分钟", value: "minute" },
  { label: "小时", value: "hour" },
  { label: "天", value: "day" },
  { label: "周", value: "week" },
];

onMounted(async () => {
  await Promise.all([loadDashboard(), loadTraffic()]);
  window.addEventListener("resize", resizeTrafficChart);
});
onBeforeUnmount(() => { window.removeEventListener("resize", resizeTrafficChart); trafficChartInstance?.dispose(); });

async function loadDashboard() {
  loading.value = true;
  try {
    stats.value = await api.dashboard(filters.start, filters.end);
    storageLoading.value = true;
    api.storageStats().then((value) => { storageStats.value = value; }).catch(() => { storageStats.value = null; }).finally(() => { storageLoading.value = false; });
  } finally { loading.value = false; }
}

async function loadTraffic(grain: TrafficGrain = trafficGrain.value) {
  trafficGrain.value = grain;
  trafficLoading.value = true;
  try { traffic.value = await api.peakTraffic(grain); await nextTick(); renderTrafficChart(); }
  catch (error) { ElMessage.error(error instanceof Error ? error.message : "峰值流量加载失败"); }
  finally { trafficLoading.value = false; }
}

function resizeTrafficChart() { trafficChartInstance?.resize(); }

function renderTrafficChart() {
  if (!trafficChart.value) return;
  trafficChartInstance ??= init(trafficChart.value);
  const rows = traffic.value?.rows ?? [];
  const zoomStart = trafficGrain.value === "minute" ? 98 : trafficGrain.value === "hour" ? 70 : 0;
  trafficChartInstance.setOption({
    animationDuration: 350,
    color: ["#176b55", "#d66a57"],
    grid: { left: 52, right: 28, top: 36, bottom: rows.length > 30 ? 72 : 48 },
    tooltip: {
      trigger: "axis",
      formatter(params: Array<{ dataIndex: number; marker: string; seriesName: string; value: number }>) {
        const index = params[0]?.dataIndex ?? 0;
        const row = rows[index];
        if (!row) return "";
        return `<strong>${row.periodLabel}</strong><br/>${params.map((item) => `${item.marker}${item.seriesName}：${item.value}`).join("<br/>")}<br/><span style="color:#7b8b85">平均响应：${row.avgResponseMs} ms</span>`;
      },
    },
    legend: { top: 0, right: 0, data: ["请求数", "5xx 错误"] },
    xAxis: { type: "category", boundaryGap: false, data: rows.map((row) => row.periodLabel), axisLabel: { color: "#71827c", hideOverlap: true }, axisLine: { lineStyle: { color: "#dce6e2" } } },
    yAxis: { type: "value", minInterval: 1, name: "请求数", nameTextStyle: { color: "#71827c" }, splitLine: { lineStyle: { color: "#edf2f0" } } },
    dataZoom: rows.length > 30 ? [{ type: "inside", start: zoomStart, end: 100 }, { type: "slider", start: zoomStart, end: 100, height: 18, bottom: 8, borderColor: "#dce6e2", fillerColor: "rgba(23,107,85,.12)" }] : [],
    series: [
      { name: "请求数", type: "line", smooth: true, showSymbol: rows.length < 80, symbolSize: 7, lineStyle: { width: 3 }, areaStyle: { color: "rgba(23,107,85,.10)" }, data: rows.map((row) => row.requestCount), markPoint: { symbolSize: 48, data: [{ type: "max", name: "峰值" }] } },
      { name: "5xx 错误", type: "line", smooth: true, showSymbol: false, lineStyle: { width: 2, type: "dashed" }, data: rows.map((row) => row.errorCount) },
    ],
  }, true);
}

async function refreshStorage() {
  storageLoading.value = true;
  try { storageStats.value = await api.storageStats(true); ElMessage.success("存储统计已刷新"); }
  finally { storageLoading.value = false; }
}

function formatBytes(value: unknown) {
  if (typeof value !== "number") return "无法统计";
  const units = ["B", "KB", "MB", "GB", "TB"];
  let size = value; let index = 0;
  while (size >= 1024 && index < units.length - 1) { size /= 1024; index += 1; }
  return `${size.toFixed(index ? 2 : 0)} ${units[index]}`;
}

function formatMoney(value?: number | null, currency = "CNY") {
  if (typeof value !== "number") return "-";
  if (currency === "MIXED") return "多币种，见明细";
  return `${currency || "CNY"} ${value.toFixed(6)}`;
}

function modelSceneLabel(scene: string) {
  return ({ background_report: "周期报告", background_summary: "摘要沉淀", fixed_info: "固定信息问答", simple_knowledge: "简单知识问答", knowledge_grounded: "知识问答", general_chat: "通用对话" } as Record<string, string>)[scene] || scene || "未分类";
}
</script>

<template>
  <section v-loading="loading" class="feature-page dashboard-page">
    <header class="dashboard-hero">
      <div>
        <span class="dashboard-eyebrow">OPERATIONS OVERVIEW</span>
        <h2>数据看板</h2>
        <p>从业务规模、请求峰值到模型成本，快速判断系统运行状态。</p>
      </div>
      <div class="dashboard-date-range" aria-label="看板日期范围">
        <label class="dashboard-date-field"><span>开始日期</span><input v-model="filters.start" type="date" aria-label="开始日期" @change="loadDashboard" /></label>
        <span class="record-date-sep">至</span>
        <label class="dashboard-date-field"><span>结束日期</span><input v-model="filters.end" type="date" aria-label="结束日期" @change="loadDashboard" /></label>
      </div>
    </header>

    <section class="overview-grid" aria-label="核心业务指标">
      <article class="overview-card primary"><span>用户总数</span><strong>{{ stats?.userCount ?? 0 }}</strong><small>当前已沉淀用户</small></article>
      <article class="overview-card"><span>会话数</span><strong>{{ stats?.sessionCount ?? 0 }}</strong><small>选定日期范围</small></article>
      <article class="overview-card"><span>消息数</span><strong>{{ stats?.messageCount ?? 0 }}</strong><small>用户与 Agent 对话</small></article>
      <article class="overview-card"><span>AI 请求</span><strong>{{ stats?.aiRequestCount ?? 0 }}</strong><small>模型实际调用次数</small></article>
      <article class="overview-card"><span>估算成本</span><strong class="money">{{ formatMoney(stats?.estimatedCost, stats?.costCurrency || "CNY") }}</strong><small>按已配置模型单价</small></article>
    </section>

    <section class="dashboard-panel traffic-panel" v-loading="trafficLoading">
      <div class="panel-heading">
        <div><span class="section-kicker">流量监测</span><h3>峰值流量</h3><p>折线图展示最近 7 天变化趋势，可切换时间粒度查看。</p></div>
        <div class="grain-switch" role="group" aria-label="流量统计粒度">
          <button v-for="option in trafficOptions" :key="option.value" type="button" :class="{ active: trafficGrain === option.value }" @click="loadTraffic(option.value)">{{ option.label }}</button>
        </div>
      </div>
      <div class="traffic-summary">
        <div><span>峰值时段</span><strong>{{ traffic?.peakPeriod || "暂无数据" }}</strong></div>
        <div><span>峰值请求数</span><strong>{{ traffic?.peakRequests ?? 0 }}</strong></div>
        <div><span>近 7 天总请求</span><strong>{{ traffic?.totalRequests ?? 0 }}</strong></div>
      </div>
      <div v-if="traffic?.rows.length" ref="trafficChart" class="traffic-chart" role="img" aria-label="最近七天峰值流量折线图"></div>
      <el-empty v-else description="暂无流量数据，新请求产生后会自动统计" :image-size="72" />
    </section>

    <section class="dashboard-panel">
      <div class="panel-heading"><div><span class="section-kicker">模型资源</span><h3>模型使用与成本</h3><p>核对模型分流、Token 消耗和费用。</p></div><div class="token-summary"><span>总 Token</span><strong>{{ stats?.totalToken ?? 0 }}</strong><small>输入 {{ stats?.inputToken ?? 0 }} · 输出 {{ stats?.outputToken ?? 0 }}</small></div></div>
      <el-table :data="stats?.costBreakdown || []" empty-text="当前日期范围内暂无模型调用">
        <el-table-column label="调用场景" min-width="130"><template #default="{ row }">{{ modelSceneLabel(row.scene) }}</template></el-table-column>
        <el-table-column prop="modelName" label="实际模型" min-width="180" show-overflow-tooltip />
        <el-table-column prop="requestCount" label="请求数" width="100" /><el-table-column prop="inputToken" label="输入 Token" width="130" /><el-table-column prop="outputToken" label="输出 Token" width="130" />
        <el-table-column label="估算成本" width="170"><template #default="{ row }">{{ formatMoney(row.estimatedCost, row.currency || "CNY") }}</template></el-table-column>
      </el-table>
    </section>

    <section class="dashboard-panel" v-loading="storageLoading">
      <div class="panel-heading"><div><span class="section-kicker">基础设施</span><h3>项目存储占用</h3><p>数据库、Redis 与附件分别统计，无法取得时明确标记。</p></div><el-button :loading="storageLoading" @click="refreshStorage">手动刷新</el-button></div>
      <div class="storage-grid"><div><span>项目总量</span><strong>{{ formatBytes(storageStats?.totalBytes) }}</strong></div><div><span>数据库</span><strong>{{ formatBytes(storageStats?.detail?.database?.bytes) }}</strong></div><div><span>Redis</span><strong>{{ formatBytes(storageStats?.detail?.redis?.bytes) }}</strong></div><div><span>附件/文件</span><strong>{{ formatBytes(storageStats?.detail?.files?.bytes) }}</strong></div><div><span>近7天增长</span><strong>{{ formatBytes(storageStats?.growth7DaysBytes) }}</strong></div><div><span>近30天增长</span><strong>{{ formatBytes(storageStats?.growth30DaysBytes) }}</strong></div></div>
      <small class="storage-updated">最后统计：{{ storageStats?.createdAt || "尚未统计" }}</small>
    </section>
  </section>
</template>

<style scoped>
.dashboard-page{--dash-border:#dce6e2;--dash-muted:#687b74;--dash-green:#176b55;display:grid;gap:20px}.dashboard-hero{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding:24px 26px;border:1px solid var(--dash-border);border-radius:14px;background:linear-gradient(135deg,#f5fbf8 0%,#fff 64%)}.dashboard-eyebrow,.section-kicker{display:block;margin-bottom:6px;color:#2a8069;font-size:11px;font-weight:700;letter-spacing:.12em}.dashboard-hero h2,.panel-heading h3{margin:0}.dashboard-hero h2{font-size:28px}.dashboard-hero p,.panel-heading p{margin:7px 0 0;color:var(--dash-muted);line-height:1.5}.dashboard-date-range{padding:8px;border:1px solid var(--dash-border);border-radius:10px;background:#fff}.dashboard-date-field{display:flex;align-items:center;gap:8px}.dashboard-date-field span{color:var(--dash-muted);font-size:12px;white-space:nowrap}.dashboard-date-field input{height:34px;border:0;outline:0;color:#283631;background:transparent}.overview-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.overview-card{min-height:126px;padding:18px;border:1px solid var(--dash-border);border-radius:12px;background:#fff}.overview-card.primary{border-color:#b9d9ce;background:#f0f8f5}.overview-card span,.traffic-summary span,.token-summary span{color:var(--dash-muted);font-size:13px}.overview-card strong{display:block;margin:12px 0 7px;color:#1d2925;font-size:28px;line-height:1}.overview-card strong.money{font-size:20px;line-height:1.3}.overview-card small,.token-summary small{color:#8a9a94}.dashboard-panel{padding:22px;border:1px solid var(--dash-border);border-radius:14px;background:#fff}.panel-heading{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:18px}.panel-heading h3{font-size:20px}.grain-switch{display:flex;padding:4px;border:1px solid var(--dash-border);border-radius:10px;background:#f5f8f7}.grain-switch button{min-width:56px;height:34px;padding:0 13px;border:0;border-radius:7px;color:#586b64;background:transparent;cursor:pointer}.grain-switch button:hover{color:var(--dash-green)}.grain-switch button.active{color:#fff;background:var(--dash-green);box-shadow:0 2px 6px rgba(23,107,85,.18)}.traffic-summary{display:grid;grid-template-columns:2fr 1fr 1fr;margin-bottom:16px;border:1px solid #e6ece9;border-radius:10px;background:#f8faf9}.traffic-summary>div{padding:15px 18px;border-right:1px solid #e6ece9}.traffic-summary>div:last-child{border-right:0}.traffic-summary strong{display:block;margin-top:6px;font-size:19px}.traffic-chart{width:100%;height:360px}.token-summary{min-width:220px;padding:12px 16px;border-radius:10px;background:#f5f8f7}.token-summary strong{display:block;margin:3px 0;font-size:22px}.storage-grid{margin:0}.storage-updated{display:block;margin-top:14px;color:#7d8e88}@media(max-width:900px){.dashboard-hero,.panel-heading{align-items:stretch;flex-direction:column}.dashboard-date-range{align-self:stretch;overflow:auto}.overview-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.traffic-summary{grid-template-columns:1fr}.traffic-summary>div{border-right:0;border-bottom:1px solid #e6ece9}.traffic-summary>div:last-child{border-bottom:0}.grain-switch{overflow:auto}.grain-switch button{flex:1}.traffic-chart{height:320px}}@media(max-width:560px){.overview-grid{grid-template-columns:1fr}.dashboard-panel,.dashboard-hero{padding:16px}}
</style>
