<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { useRoute } from 'vue-router'
import type { EChartsCoreOption } from 'echarts'
import { useChart } from '@/composables/useChart'
import { darkTitle, darkTooltip, darkGrid, darkAxis } from '@/utils/echartsDark'
import {
  postPortfolioBacktest,
  getPortfolioTaskStatus,
  getPortfolioResult,
  type PortfolioBacktestResult,
  type EquityPoint,
  type StockPicked,
  type PortfolioTrade,
  type PortfolioMetrics,
} from '@/api/portfolio'

const submitting = ref(false)
const result = ref<PortfolioBacktestResult | null>(null)
const equityCurve = ref<EquityPoint[]>([])
const stocks = ref<StockPicked[]>([])
const trades = ref<PortfolioTrade[]>([])
const metrics = ref<PortfolioMetrics | null>(null)
const period = ref<{ start: string; end: string; trading_days: number } | null>(null)
const errorMsg = ref('')
const stageHint = ref('')
let pollTimer: ReturnType<typeof setInterval> | null = null

function loadDateRange(): { start: string; end: string } {
  try {
    const s = localStorage.getItem('portfolio_date_range')
    if (s) return JSON.parse(s) as { start: string; end: string }
  } catch {
    /* ignore */
  }
  const end = new Date()
  const start = new Date(end)
  start.setFullYear(start.getFullYear() - 1)
  const f = (d: Date): string => d.toISOString().slice(0, 10)
  return { start: f(start), end: f(end) }
}
const dr = loadDateRange()
const form = reactive({
  pool: 'hs300_subset',
  strategy: 'all_weather',
  max_pool: 30,
  start: dr.start,
  end: dr.end,
  cash: 1_000_000,
  benchmark: '000300.XSHG',
})
watch(
  () => [form.start, form.end],
  ([s, e]) => {
    try {
      localStorage.setItem('portfolio_date_range', JSON.stringify({ start: s, end: e }))
    } catch {
      /* ignore */
    }
  },
)

const poolOptions = [
  { label: 'HS300 子集(小范围验证)', value: 'hs300_subset' },
  { label: '全市场(慢,非 MVP)', value: 'all' },
]

const strategyOptions = [
  { label: '全天候轮动', value: 'all_weather' },
  { label: '牛熊动量', value: 'momentum_timing' },
  { label: '价值精选', value: 'value_selection' },
  { label: '小市值轮动', value: 'small_cap' },
]
const BENCHMARK_OPTIONS = [
  { label: '沪深300', value: '000300.XSHG' },
  { label: '中证500', value: '000905.XSHG' },
  { label: '中证1000', value: '000852.XSHG' },
  { label: '中证2000', value: '932000.XSHG' },
]

const strategyLabel = computed(
  () => strategyOptions.find((o) => o.value === form.strategy)?.label ?? form.strategy,
)

// 净值曲线
const equityEl = ref<HTMLDivElement>()
const { setOption: setEquityOption } = useChart(equityEl)
function renderEquity(): void {
  if (!equityCurve.value.length) return
  const dates = equityCurve.value.map((p) => p.date)
  const values = equityCurve.value.map((p) => p.equity)
  const option: EChartsCoreOption = {
    title: darkTitle('净值曲线'),
    tooltip: darkTooltip(),
    grid: darkGrid(),
    xAxis: { type: 'category', data: dates, ...darkAxis() },
    yAxis: { type: 'value', scale: true, name: '净值(元)', ...darkAxis() },
    series: [
      {
        type: 'line',
        name: '策略净值',
        smooth: true,
        showSymbol: false,
        lineStyle: { color: '#c23531', width: 1.6 },
        areaStyle: { color: '#c23531', opacity: 0.12 },
        data: values,
      },
    ],
  }
  setEquityOption(option)
}
const route = useRoute()
const viewTaskId = computed(() => (route.query.task_id as string) || '')
const elapsedSec = ref(0)
let elapsedTimer: ReturnType<typeof setInterval> | null = null

async function loadResult(tid: string): Promise<void> {
  try {
    const r = await getPortfolioResult(tid)
    result.value = r
    equityCurve.value = r.equity_curve || []
    stocks.value = r.stocks_selected || []
    trades.value = r.trades || []
    metrics.value = r.metrics || null
    period.value = r.period || null
    await nextTick()
    renderEquity()
  } catch {
    ElMessage.error('加载历史结果失败')
  }
}

onMounted(() => {
  if (viewTaskId.value) loadResult(viewTaskId.value)
})
onUnmounted(() => {
  if (pollTimer) clearInterval(pollTimer)
})
watch(equityCurve, renderEquity, { deep: true, flush: 'post' })

function fmtPct(v: number | null | undefined): string {
  if (v === null || v === undefined || !Number.isFinite(v)) return '-'
  return `${v.toFixed(2)}%`
}
function fmtNum(v: number | null | undefined, digits = 2): string {
  if (v === null || v === undefined || !Number.isFinite(v)) return '-'
  return v.toFixed(digits)
}

async function onSubmit(): Promise<void> {
  submitting.value = true
  errorMsg.value = ''
  stageHint.value = ''
  elapsedSec.value = 0
  elapsedTimer = setInterval(() => {
    elapsedSec.value += 1
  }, 1000)
  result.value = null
  equityCurve.value = []
  stocks.value = []
  trades.value = []
  metrics.value = null
  try {
    const tid = await postPortfolioBacktest({
      pool: form.pool,
      strategy: form.strategy,
      max_pool: form.max_pool,
      start_date: form.start,
      end_date: form.end,
      initial_cash: form.cash,
      benchmark: form.benchmark,
    })
    // Poll task status until done/failed, then fetch result
    const r = await new Promise<PortfolioBacktestResult>((resolve, reject) => {
      pollTimer = setInterval(async () => {
        try {
          const s = await getPortfolioTaskStatus(tid)
          if (s.stage) stageHint.value = s.stage
          if (s.status === 'done') {
            if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
            const res = await getPortfolioResult(tid)
            resolve(res)
          } else if (s.status === 'failed') {
            if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
            reject(new Error(s.error_msg || '回测失败'))
          }
          // pending/running: keep polling
        } catch (pollErr) {
          // transient network error on poll — keep polling
        }
      }, 2500)
    })
    result.value = r
    equityCurve.value = r.equity_curve || []
    stocks.value = r.stocks_selected || []
    trades.value = r.trades || []
    metrics.value = r.metrics || null
    period.value = r.period || null
    // v-if result 块(含 chart-box div)在此帧才挂载, watch(equityCurve, flush:post)
    // 在异步轮询 done 后赋值时序下会漏触发 echarts.init → 净值曲线空白。
    // nextTick 等 chart-box 挂载后手动渲染兜底。
    await nextTick()
    renderEquity()
    ElMessage.success(
      `回测完成: ${r.period?.trading_days ?? 0} 交易日, 选股 ${(r.stocks_selected || []).length} 只`,
    )
  } catch (e: unknown) {
    const err = e as { response?: { data?: { detail?: string } }; message?: string }
    errorMsg.value = err.response?.data?.detail || err.message || '提交失败'
    ElMessage.error('回测失败,详见页面提示')
  } finally {
    submitting.value = false
    if (elapsedTimer) {
      clearInterval(elapsedTimer)
      elapsedTimer = null
    }
  }
}
</script>

<template>
  <div class="page bt-portfolio">
    <div class="page-head">
      <div>
        <h2 class="page-title">{{ viewTaskId ? '组合回测结果' : '组合策略回测' }}</h2>
        <p class="page-subtitle">
          BulletTrade + {{ strategyLabel }} · 本地执行回测 · MVP 验证链路
        </p>
      </div>
    </div>

    <el-card v-if="!viewTaskId" class="blk" shadow="never">
      <template #header><span class="section-title">回测参数</span></template>
      <el-form :model="form" label-width="120px">
        <el-form-item label="标的池">
          <el-select v-model="form.pool" style="width: 320px">
            <el-option
              v-for="opt in poolOptions"
              :key="opt.value"
              :label="opt.label"
              :value="opt.value"
            />
          </el-select>
          <span class="muted form-hint">MVP 默认 HS300 子集(20-30 只),快速验证链路</span>
        </el-form-item>
        <el-form-item label="策略">
          <el-select v-model="form.strategy" style="width: 320px">
            <el-option
              v-for="opt in strategyOptions"
              :key="opt.value"
              :label="opt.label"
              :value="opt.value"
            />
          </el-select>
          <span class="muted form-hint">选择回测策略</span>
        </el-form-item>
        <el-form-item label="选股池上限">
          <el-input-number
            v-model="form.max_pool"
            :min="0"
            :step="10"
            :controls="false"
            style="width: 220px"
          />
          <span class="muted form-hint">0=全市场不限, N=前N只(MVP验证用, 默认30)</span>
        </el-form-item>
        <el-form-item label="开始日期">
          <el-date-picker
            v-model="form.start"
            type="date"
            value-format="YYYY-MM-DD"
            style="width: 220px"
          />
        </el-form-item>
        <el-form-item label="结束日期">
          <el-date-picker
            v-model="form.end"
            type="date"
            value-format="YYYY-MM-DD"
            style="width: 220px"
          />
        </el-form-item>
        <el-form-item label="初始资金">
          <el-input-number
            v-model="form.cash"
            :min="10000"
            :step="100000"
            :controls="false"
            style="width: 220px"
          />
          <span class="muted form-hint">元</span>
        </el-form-item>
        <el-form-item label="基准">
          <el-select v-model="form.benchmark" style="width: 220px">
            <el-option v-for="b in BENCHMARK_OPTIONS" :key="b.value" :label="b.label" :value="b.value" />
          </el-select>
        </el-form-item>
      </el-form>
      <div v-if="!viewTaskId" class="submit-bar">
        <el-button
          type="primary"
          size="large"
          :loading="submitting"
          @click="onSubmit"
        >
          {{ submitting ? `回测中${stageHint ? '(' + stageHint + ')' : '...'}` : '开始回测' }}
        </el-button>
        <span v-if="submitting" class="muted form-hint">
          异步执行 · 已等待 {{ elapsedSec }}s · 组合回测加载大库数据预计 1-3 分钟,请勿关闭
        </span>
      </div>
    </el-card>

    <el-alert
      v-if="errorMsg"
      type="error"
      :title="`回测失败:${errorMsg}`"
      :closable="false"
      show-icon
      class="blk"
    />

    <template v-if="result">
      <!-- 指标卡片 -->
      <el-card class="blk" shadow="never">
        <template #header>
          <span class="section-title">关键指标</span>
          <span class="muted period">
            ({{ period?.start }} ~ {{ period?.end }} · {{ period?.trading_days ?? 0 }} 交易日)
          </span>
        </template>
        <div class="metric-row">
          <div class="metric-cell">
            <div class="metric-label">总收益</div>
            <div class="metric-value" :class="(metrics?.total_return ?? 0) >= 0 ? 'up' : 'down'">
              {{ fmtPct(metrics?.total_return) }}
            </div>
          </div>
          <div class="metric-cell">
            <div class="metric-label">年化收益</div>
            <div class="metric-value" :class="(metrics?.annual_return ?? 0) >= 0 ? 'up' : 'down'">
              {{ fmtPct(metrics?.annual_return) }}
            </div>
          </div>
          <div class="metric-cell">
            <div class="metric-label">最大回撤</div>
            <div class="metric-value down">{{ fmtPct(metrics?.max_drawdown) }}</div>
          </div>
          <div class="metric-cell">
            <div class="metric-label">夏普比率</div>
            <div class="metric-value">{{ fmtNum(metrics?.sharpe) }}</div>
          </div>
          <div class="metric-cell">
            <div class="metric-label">日胜率</div>
            <div class="metric-value">{{ fmtPct(metrics?.win_rate_daily) }}</div>
          </div>
          <div class="metric-cell">
            <div class="metric-label">交易胜率</div>
            <div class="metric-value">{{ fmtPct(metrics?.win_rate_trade) }}</div>
          </div>
        </div>
      </el-card>

      <!-- 净值曲线 -->
      <el-card class="blk" shadow="never">
        <template #header><span class="section-title">净值曲线</span></template>
        <div ref="equityEl" class="chart-box" />
      </el-card>

      <!-- 选股名单 -->
      <el-card class="blk" shadow="never">
        <template #header>
          <span class="section-title">选股名单(末日持仓)</span>
        </template>
        <el-table :data="stocks" stripe size="small" empty-text="无持仓数据">
          <el-table-column label="代码" prop="code" width="140">
            <template #default="{ row }"><span class="mono">{{ row.code }}</span></template>
          </el-table-column>
          <el-table-column label="数量" prop="amount" align="right" width="140">
            <template #default="{ row }"><span class="mono">{{ row.amount.toFixed(0) }}</span></template>
          </el-table-column>
          <el-table-column label="均价" align="right" width="120">
            <template #default="{ row }"><span class="mono">{{ row.avg_cost.toFixed(3) }}</span></template>
          </el-table-column>
          <el-table-column label="现价" align="right" width="120">
            <template #default="{ row }"><span class="mono">{{ row.price.toFixed(3) }}</span></template>
          </el-table-column>
          <el-table-column label="市值" align="right" width="160">
            <template #default="{ row }"><span class="mono">{{ row.value.toFixed(2) }}</span></template>
          </el-table-column>
        </el-table>
      </el-card>

      <!-- 成交明细 -->
      <el-card class="blk" shadow="never">
        <template #header><span class="section-title">成交明细({{ trades.length }} 条)</span></template>
        <el-table :data="trades" stripe size="small" max-height="500" empty-text="无成交">
          <el-table-column label="时间" width="180">
            <template #default="{ row }"><span class="mono">{{ row.datetime || row.date || '-' }}</span></template>
          </el-table-column>
          <el-table-column label="代码" prop="code" width="140">
            <template #default="{ row }"><span class="mono">{{ row.code || '-' }}</span></template>
          </el-table-column>
          <el-table-column label="方向" width="100">
            <template #default="{ row }">{{ row.side || row.action || '-' }}</template>
          </el-table-column>
          <el-table-column label="数量" align="right" width="120">
            <template #default="{ row }"><span class="mono">{{ (row.filled_amount ?? row.amount ?? 0).toFixed(0) }}</span></template>
          </el-table-column>
          <el-table-column label="价格" align="right" width="120">
            <template #default="{ row }"><span class="mono">{{ (row.filled_price ?? row.price ?? 0).toFixed(3) }}</span></template>
          </el-table-column>
        </el-table>
      </el-card>
    </template>
  </div>
</template>

<style scoped>
.bt-portfolio { display: flex; flex-direction: column; gap: 16px; }
.blk { border: 1px solid var(--border-2); }
.section-title { font-size: 14px; font-weight: 600; color: var(--text); }
.period { margin-left: 12px; font-size: 12px; }

.form-hint { margin-left: 10px; }
.submit-bar { padding: 4px 0 8px; }

.chart-box { width: 100%; height: 360px; }

.metric-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
}
.metric-cell {
  background: var(--bg-hover);
  border: 1px solid var(--border-2);
  border-radius: 6px;
  padding: 12px 14px;
}
.metric-label { font-size: 12px; color: var(--text-3); margin-bottom: 6px; }
.metric-value { font-size: 20px; font-weight: 600; color: var(--text); font-family: var(--mono); }
.metric-value.up { color: #f56c6c; }
.metric-value.down { color: #67c23a; }
/* A 股惯例涨红跌绿;回撤是负值用绿色(表示"少亏方向")但习惯 down class 显绿 */

.muted { color: var(--text-3); font-size: 13px; }
.mono { font-family: var(--mono); }
</style>
