<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { fetchLlmBillingOverview } from '../api/llmBilling'
import type { LlmBillingOverview } from '../types/llmBilling'

const overview = ref<LlmBillingOverview | null>(null)
const loading = ref(true)
const error = ref('')
const rangeDays = ref<7 | 14 | 30>(30)

function todayBizDt(): string {
  return new Intl.DateTimeFormat('sv-SE', {
    timeZone: 'Asia/Shanghai',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  })
    .format(new Date())
    .replaceAll('-', '')
}

function shiftBizDt(bizDt: string, days: number): string {
  const y = Number(bizDt.slice(0, 4))
  const m = Number(bizDt.slice(4, 6))
  const d = Number(bizDt.slice(6, 8))
  const date = new Date(Date.UTC(y, m - 1, d))
  date.setUTCDate(date.getUTCDate() + days)
  const yy = date.getUTCFullYear()
  const mm = String(date.getUTCMonth() + 1).padStart(2, '0')
  const dd = String(date.getUTCDate()).padStart(2, '0')
  return `${yy}${mm}${dd}`
}

async function load() {
  loading.value = true
  error.value = ''
  try {
    const endDt = todayBizDt()
    const startDt = shiftBizDt(endDt, -(rangeDays.value - 1))
    overview.value = await fetchLlmBillingOverview({
      startDt,
      endDt,
      recentLimit: 40,
    })
  } catch (cause) {
    error.value = cause instanceof Error ? cause.message : String(cause)
  } finally {
    loading.value = false
  }
}

onMounted(load)

const maxDailyCost = computed(() => {
  const values = overview.value?.daily.map((d) => d.total_cost_usd) ?? []
  return Math.max(...values, 0.000001)
})

const maxAgentCost = computed(() => {
  const values = overview.value?.by_agent.map((d) => d.total_cost_usd) ?? []
  return Math.max(...values, 0.000001)
})

const maxModelCost = computed(() => {
  const values = overview.value?.by_model.map((d) => d.total_cost_usd) ?? []
  return Math.max(...values, 0.000001)
})

function formatUsd(value: number | null | undefined, digits = 4): string {
  if (value == null || Number.isNaN(value)) return '—'
  if (value === 0) return '$0'
  if (value < 0.0001) return `$${value.toExponential(2)}`
  return `$${value.toFixed(digits)}`
}

function formatTokens(value: number): string {
  if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M`
  if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`
  return String(value)
}

function shortModel(model: string): string {
  const parts = model.split('/')
  return parts[parts.length - 1] || model
}

function barHeight(cost: number, max: number): string {
  const pct = Math.max(3, (cost / max) * 100)
  return `${pct}%`
}

function barWidth(cost: number, max: number): string {
  return `${Math.max(2, (cost / max) * 100)}%`
}
</script>

<template>
  <main class="billing-page">
    <header class="page-header">
      <div>
        <div class="eyebrow"><span /> LLM BILLING</div>
        <h1>LLM 费用看板</h1>
        <p>按天汇总全部 Agent 的 OpenRouter 调用费用（Asia/Shanghai）。</p>
      </div>
      <div class="range-switch" role="group" aria-label="时间范围">
        <button
          v-for="days in [7, 14, 30] as const"
          :key="days"
          type="button"
          :class="{ active: rangeDays === days }"
          :disabled="loading"
          @click="rangeDays = days; load()"
        >
          近 {{ days }} 天
        </button>
      </div>
    </header>

    <p v-if="error" class="error">{{ error }}</p>
    <p v-else-if="loading && !overview" class="loading">加载中…</p>

    <template v-if="overview">
      <section class="summary-grid" aria-label="费用摘要">
        <article class="summary-card accent">
          <span>今日费用</span>
          <strong>{{ formatUsd(overview.summary.today_cost_usd) }}</strong>
          <small>{{ overview.summary.today_call_count }} 次调用 · {{ overview.summary.today_biz_dt }}</small>
        </article>
        <article class="summary-card">
          <span>昨日费用</span>
          <strong>{{ formatUsd(overview.summary.yesterday_cost_usd) }}</strong>
          <small>对比今日趋势</small>
        </article>
        <article class="summary-card">
          <span>近 7 天</span>
          <strong>{{ formatUsd(overview.summary.week_cost_usd) }}</strong>
          <small>滚动一周合计</small>
        </article>
        <article class="summary-card">
          <span>区间合计</span>
          <strong>{{ formatUsd(overview.range.total_cost_usd) }}</strong>
          <small>
            {{ formatTokens(overview.range.total_tokens) }} tokens ·
            {{ overview.range.call_count }} 次
          </small>
        </article>
      </section>

      <section class="panel chart-panel">
        <div class="panel-head">
          <div>
            <h2>每日费用</h2>
            <p>{{ overview.range.start_dt }} — {{ overview.range.end_dt }}</p>
          </div>
          <span class="currency-tag">USD</span>
        </div>
        <div class="bar-chart" role="img" :aria-label="`近 ${rangeDays} 天每日 LLM 费用柱状图`">
          <div
            v-for="day in overview.daily"
            :key="day.biz_dt"
            class="bar-col"
            :title="`${day.label}: ${formatUsd(day.total_cost_usd)} · ${day.call_count} 次`"
          >
            <span class="bar-value">{{ day.total_cost_usd > 0 ? formatUsd(day.total_cost_usd, 3) : '' }}</span>
            <div class="bar-track">
              <div
                class="bar-fill"
                :class="{ empty: day.total_cost_usd <= 0 }"
                :style="{ height: day.total_cost_usd > 0 ? barHeight(day.total_cost_usd, maxDailyCost) : '2px' }"
              />
            </div>
            <span class="bar-label">{{ day.label.slice(5) }}</span>
          </div>
        </div>
      </section>

      <section class="split-panels">
        <article class="panel">
          <div class="panel-head">
            <h2>按 Agent</h2>
            <p>区间费用占比</p>
          </div>
          <ul v-if="overview.by_agent.length" class="breakdown-list">
            <li v-for="item in overview.by_agent" :key="item.agent_name">
              <div class="row-meta">
                <strong>{{ item.agent_name }}</strong>
                <span>{{ formatUsd(item.total_cost_usd) }}</span>
              </div>
              <div class="h-track">
                <div class="h-fill agent" :style="{ width: barWidth(item.total_cost_usd, maxAgentCost) }" />
              </div>
              <small>{{ item.call_count }} 次 · {{ formatTokens(item.total_tokens) }} tokens</small>
            </li>
          </ul>
          <p v-else class="empty">暂无 Agent 调用记录</p>
        </article>

        <article class="panel">
          <div class="panel-head">
            <h2>按模型</h2>
            <p>区间费用占比</p>
          </div>
          <ul v-if="overview.by_model.length" class="breakdown-list">
            <li v-for="item in overview.by_model" :key="item.model">
              <div class="row-meta">
                <strong :title="item.model">{{ shortModel(item.model) }}</strong>
                <span>{{ formatUsd(item.total_cost_usd) }}</span>
              </div>
              <div class="h-track">
                <div class="h-fill model" :style="{ width: barWidth(item.total_cost_usd, maxModelCost) }" />
              </div>
              <small>{{ item.call_count }} 次 · {{ formatTokens(item.total_tokens) }} tokens</small>
            </li>
          </ul>
          <p v-else class="empty">暂无模型调用记录</p>
        </article>
      </section>

      <section class="panel">
        <div class="panel-head">
          <div>
            <h2>最近调用</h2>
            <p>按写入时间倒序</p>
          </div>
        </div>
        <div class="table-wrap">
          <table>
            <thead>
              <tr>
                <th>时间</th>
                <th>Agent</th>
                <th>模型</th>
                <th>Tokens</th>
                <th>费用</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="event in overview.recent_events" :key="event.id">
                <td>{{ event.created_at || '—' }}</td>
                <td>{{ event.agent_name || '—' }}</td>
                <td :title="event.model || undefined">{{ event.model ? shortModel(event.model) : '—' }}</td>
                <td>{{ formatTokens(event.total_tokens) }}</td>
                <td>{{ formatUsd(event.cost_usd) }}</td>
              </tr>
              <tr v-if="!overview.recent_events.length">
                <td colspan="5" class="empty-cell">暂无调用明细。Agent 下次调用 LLM 后将自动写入。</td>
              </tr>
            </tbody>
          </table>
        </div>
      </section>
    </template>
  </main>
</template>

<style scoped>
.billing-page {
  --ink: #172033;
  --muted: #6c7484;
  --line: #e7e9ef;
  --panel: #ffffff;
  --accent: #1f6bff;
  --accent-soft: #eaf1ff;
  max-width: 1240px;
  margin: 0 auto;
  padding: 32px 24px 72px;
  color: var(--ink);
}

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 28px;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  letter-spacing: 0.14em;
  color: var(--muted);
}

.eyebrow span {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--accent);
}

h1 {
  margin: 8px 0 6px;
  font-size: 28px;
  letter-spacing: -0.02em;
}

.page-header p,
.panel-head p {
  margin: 0;
  color: var(--muted);
}

.range-switch {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.range-switch button {
  border: 1px solid #cbd2df;
  background: #fff;
  border-radius: 999px;
  padding: 8px 14px;
  cursor: pointer;
  color: #44506a;
}

.range-switch button.active {
  background: #1e2638;
  border-color: #1e2638;
  color: #fff;
}

.summary-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.summary-card,
.panel {
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--panel);
}

.summary-card {
  padding: 18px 18px 16px;
  display: grid;
  gap: 6px;
}

.summary-card span,
.summary-card small {
  color: var(--muted);
  font-size: 13px;
}

.summary-card strong {
  font-size: 28px;
  letter-spacing: -0.03em;
}

.summary-card.accent {
  background: linear-gradient(160deg, #f7f9ff, var(--accent-soft));
  border-color: #d7e3ff;
}

.summary-card.accent strong {
  color: #1848b8;
}

.panel {
  padding: 20px;
  margin-bottom: 18px;
}

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

.panel-head h2 {
  margin: 0 0 4px;
  font-size: 18px;
}

.currency-tag {
  font-size: 12px;
  letter-spacing: 0.08em;
  color: #5b6b88;
  background: #f3f5f9;
  border-radius: 999px;
  padding: 6px 10px;
}

.bar-chart {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(28px, 1fr);
  gap: 8px;
  align-items: end;
  min-height: 220px;
  overflow-x: auto;
  padding-bottom: 4px;
}

.bar-col {
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: 6px;
  min-height: 200px;
  justify-items: center;
}

.bar-value {
  font-size: 10px;
  color: #70809c;
  min-height: 14px;
  white-space: nowrap;
}

.bar-track {
  width: 100%;
  max-width: 36px;
  height: 160px;
  display: flex;
  align-items: flex-end;
  background: #f5f7fb;
  border-radius: 10px 10px 4px 4px;
  overflow: hidden;
}

.bar-fill {
  width: 100%;
  border-radius: 10px 10px 4px 4px;
  background: linear-gradient(180deg, #4d84ff, #1f6bff);
}

.bar-fill.empty {
  background: #d9dee8;
}

.bar-label {
  font-size: 11px;
  color: var(--muted);
}

.split-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  margin-bottom: 18px;
}

.split-panels .panel {
  margin-bottom: 0;
}

.breakdown-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
}

.row-meta {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}

.row-meta strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.h-track {
  height: 8px;
  border-radius: 999px;
  background: #eef1f6;
  overflow: hidden;
}

.h-fill {
  height: 100%;
  border-radius: 999px;
}

.h-fill.agent {
  background: linear-gradient(90deg, #4d84ff, #1f6bff);
}

.h-fill.model {
  background: linear-gradient(90deg, #36c2a5, #159a7c);
}

.breakdown-list small,
.empty,
.loading {
  color: var(--muted);
}

.table-wrap {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

th,
td {
  text-align: left;
  padding: 10px 8px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

th {
  color: var(--muted);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.04em;
}

.empty-cell {
  text-align: center;
  color: var(--muted);
  padding: 28px 8px;
}

.error {
  color: #b42335;
  margin-bottom: 16px;
}

@media (max-width: 900px) {
  .summary-grid,
  .split-panels {
    grid-template-columns: 1fr 1fr;
  }

  .page-header {
    flex-direction: column;
  }
}

@media (max-width: 640px) {
  .summary-grid,
  .split-panels {
    grid-template-columns: 1fr;
  }
}
</style>
