// ═══════════════════════════════════════════════════════════
//  資料引擎 — 負責「抓真實資料」並換算成卡片要顯示的數字
//
//  流程：FRED / Stooq 取得原始時間序列
//       → computeMetric() 依設定做 YoY / MoM / 變動量等換算
//       → 取最新值當顯示值、與前一期比較算「變動」
//       → 取近期資料做成 sparkline 走勢圖（真實，不再是假的）
//       → formatValue() 套上 % / bp / $ / K 等單位
// ═══════════════════════════════════════════════════════════

import { INDICATORS } from './indicators.js';
import { buildHistoricalContext } from './context.js';

const FRED_BASE = 'https://api.stlouisfed.org/fred/series/observations';
const YAHOO_BASE = 'https://query1.finance.yahoo.com/v8/finance/chart/';

// 自訂錯誤：金鑰未設定時用，讓 API 端點能回傳友善訊息
export class MissingKeyError extends Error {}

function getApiKey() {
  const key = process.env.FRED_API_KEY;
  if (!key || key === 'your_fred_api_key_here') {
    throw new MissingKeyError('尚未設定 FRED_API_KEY');
  }
  return key;
}

function isoDaysAgo(days) {
  const d = new Date(Date.now() - days * 86400000);
  return d.toISOString().slice(0, 10);
}

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// 併發限制器：FRED 對「短時間內大量同時請求」會回 429，
// 因此一次最多只放行 5 個請求，其餘排隊。
function createLimiter(max) {
  let active = 0;
  const queue = [];
  const next = () => {
    if (active >= max || queue.length === 0) return;
    active++;
    const { fn, resolve, reject } = queue.shift();
    fn().then(resolve, reject).finally(() => { active--; next(); });
  };
  return (fn) => new Promise((resolve, reject) => { queue.push({ fn, resolve, reject }); next(); });
}
const limit = createLimiter(2);

// ─── 抓取 FRED 序列 → [{date, value:Number}]（已濾掉缺值 '.'）───
//  透過限制器排隊，並對 429（請求過多）自動退避重試。
async function fetchFredSeries(seriesId, startISO) {
  const key = getApiKey();
  const url = `${FRED_BASE}?series_id=${encodeURIComponent(seriesId)}` +
    `&api_key=${key}&file_type=json&sort_order=asc&observation_start=${startISO}`;
  return limit(async () => {
    for (let attempt = 0; attempt < 9; attempt++) {
      const res = await fetch(url);
      if (res.status === 429) { // 過多請求，退避後重試（含抖動避免同步重試）
        await sleep(700 * (attempt + 1) + Math.random() * 400);
        continue;
      }
      if (!res.ok) throw new Error(`FRED ${seriesId} 回應 ${res.status}`);
      const json = await res.json();
      return (json.observations || [])
        .filter((o) => o.value !== '.' && o.value !== '' && o.value != null)
        .map((o) => ({ date: o.date, value: Number(o.value) }))
        .filter((o) => Number.isFinite(o.value));
    }
    throw new Error(`FRED ${seriesId} 持續回應 429（請稍後再試）`);
  });
}

// ─── 抓取 Yahoo Finance 行情（日線）→ [{date, value:收盤}] ───
//  伺服器對伺服器呼叫，無 CORS 問題、免金鑰；用於 FRED 無法良好提供的黃金。
async function fetchYahooSeries(symbol, range = '1y') {
  const url = `${YAHOO_BASE}${encodeURIComponent(symbol)}?range=${range}&interval=1d`;
  const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
  if (!res.ok) throw new Error(`Yahoo ${symbol} 回應 ${res.status}`);
  const json = await res.json();
  const result = json?.chart?.result?.[0];
  const ts = result?.timestamp;
  const closes = result?.indicators?.quote?.[0]?.close;
  if (!ts || !closes) throw new Error(`Yahoo ${symbol} 無資料`);
  const out = [];
  for (let i = 0; i < ts.length; i++) {
    if (closes[i] == null || !Number.isFinite(closes[i])) continue;
    out.push({ date: new Date(ts[i] * 1000).toISOString().slice(0, 10), value: closes[i] });
  }
  return out;
}

// ─── 依 transform 把原始序列換算成「要顯示的指標序列」 ───
function computeMetric(points, transform, periodsPerYear) {
  const v = points.map((p) => p.value);
  const out = [];
  switch (transform) {
    case 'level': // 直接用原值
    case 'usd':
      for (let i = 0; i < points.length; i++) out.push({ date: points[i].date, val: v[i] });
      break;
    case 'yoy': { // 年增率：與一年前比較
      const n = periodsPerYear || 12;
      for (let i = n; i < points.length; i++) {
        if (v[i - n] === 0) continue;
        out.push({ date: points[i].date, val: (v[i] / v[i - n] - 1) * 100 });
      }
      break;
    }
    case 'mom': { // 月增率：與上一期比較
      for (let i = 1; i < points.length; i++) {
        if (v[i - 1] === 0) continue;
        out.push({ date: points[i].date, val: (v[i] / v[i - 1] - 1) * 100 });
      }
      break;
    }
    case 'payems_diff': { // 非農：本期減上期（PAYEMS 單位已是千人）
      for (let i = 1; i < points.length; i++) {
        out.push({ date: points[i].date, val: v[i] - v[i - 1] });
      }
      break;
    }
    case 'percent_to_bp': // 百分比 → 基點
      for (let i = 0; i < points.length; i++) out.push({ date: points[i].date, val: v[i] * 100 });
      break;
    case 'level_per_thousand': // 人數 → 千人
      for (let i = 0; i < points.length; i++) out.push({ date: points[i].date, val: v[i] / 1000 });
      break;
    case 'millions_to_trillions': // 百萬 → 兆
      for (let i = 0; i < points.length; i++) out.push({ date: points[i].date, val: v[i] / 1e6 });
      break;
    default:
      for (let i = 0; i < points.length; i++) out.push({ date: points[i].date, val: v[i] });
  }
  return out;
}

// ─── 取近期資料、平均取樣成 sparkline（最多 24 點）───
function buildSparkline(metric) {
  if (metric.length === 0) return [];
  // 推估資料頻率（相鄰兩點的中位數天數），決定要回看多久
  const gaps = [];
  for (let i = 1; i < metric.length; i++) {
    gaps.push((new Date(metric[i].date) - new Date(metric[i - 1].date)) / 86400000);
  }
  gaps.sort((a, b) => a - b);
  const medianGap = gaps.length ? gaps[Math.floor(gaps.length / 2)] : 30;
  let spanDays;
  if (medianGap <= 4) spanDays = 365;        // 每日
  else if (medianGap <= 10) spanDays = 730;  // 每週
  else if (medianGap <= 45) spanDays = 1460; // 每月
  else spanDays = 2920;                       // 每季
  const cutoff = Date.now() - spanDays * 86400000;
  let recent = metric.filter((m) => new Date(m.date).getTime() >= cutoff);
  if (recent.length < 8) recent = metric.slice(-16); // 點太少就直接取最後 16 點
  // 平均取樣到最多 24 點
  const target = 24;
  if (recent.length <= target) return recent.map((m) => m.val);
  const step = (recent.length - 1) / (target - 1);
  const sampled = [];
  for (let i = 0; i < target; i++) sampled.push(recent[Math.round(i * step)].val);
  return sampled;
}

// ─── 數值格式化 ───
function fmtNum(n, d) {
  return n.toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d });
}
function signed(n, d) {
  return (n >= 0 ? '+' : '') + n.toFixed(d);
}

function formatValue(val, format, decimals) {
  const d = decimals ?? 2;
  switch (format) {
    case 'pct': return `${val.toFixed(d)}%`;
    case 'pct_signed': return `${signed(val, d)}%`;
    case 'bp': return `${Math.round(val)}bp`;
    case 'num0': return fmtNum(val, 0);
    case 'num1': return val.toFixed(1);
    case 'num2': return val.toFixed(2);
    case 'num2_signed': return signed(val, 2);
    case 'k': return `${Math.round(val).toLocaleString('en-US')}K`;
    case 'k_signed': return `${val >= 0 ? '+' : ''}${Math.round(val)}K`;
    case 'trillions': return `$${val.toFixed(d)}T`;
    case 'usd': return `$${val.toFixed(d)}`;
    case 'usd0': return `$${fmtNum(val, 0)}`;
    default: return val.toFixed(d);
  }
}

function formatChange(delta, format, decimals) {
  const d = decimals ?? 2;
  if (!Number.isFinite(delta)) return '';
  switch (format) {
    case 'pct':
    case 'pct_signed': return signed(delta, d); // 例：+0.03（單位同上方百分比）
    case 'num0': return `${delta >= 0 ? '+' : ''}${Math.round(delta)}`;
    case 'num1': return signed(delta, 1);
    case 'num2':
    case 'num2_signed': return signed(delta, 2);
    case 'bp': return `${delta >= 0 ? '+' : ''}${Math.round(delta)}bp`;
    case 'k':
    case 'k_signed': return `${delta >= 0 ? '+' : ''}${Math.round(delta)}K`;
    case 'trillions': return `${delta >= 0 ? '+' : '-'}$${Math.abs(delta * 1000).toFixed(0)}B`; // 兆→十億
    case 'usd': return `${delta >= 0 ? '+' : '-'}$${Math.abs(delta).toFixed(1)}`;
    case 'usd0': return `${delta >= 0 ? '+' : '-'}$${fmtNum(Math.abs(delta), 0)}`;
    default: return signed(delta, d);
  }
}

// ─── 顏色與徽章判定 ───
function classify(ind, dir) {
  const meaningful = !ind.excludeFromScore;
  const flat = dir === 'neutral';
  // 反向指標：下降才是好；一般指標：上升才是好
  const good = ind.inverted ? dir === 'down' : dir === 'up';
  let valueColorKey, changeColorKey, badgeKind;
  if (!meaningful) {
    valueColorKey = 'blue';      // 中性指標用藍色，不暗示好壞
    changeColorKey = 'text2';
    badgeKind = 'neutral';
  } else if (flat) {
    valueColorKey = 'yellow';
    changeColorKey = 'text2';
    badgeKind = 'neutral';
  } else {
    valueColorKey = good ? 'green' : 'red';
    changeColorKey = good ? 'green' : 'red';
    badgeKind = good ? 'good' : 'bad';
  }
  return { valueColorKey, changeColorKey, badgeKind, good, meaningful };
}

function dirOf(delta, scale) {
  const eps = scale * 0.0005; // 極小變動視為持平
  if (!Number.isFinite(delta)) return 'neutral';
  if (delta > eps) return 'up';
  if (delta < -eps) return 'down';
  return 'neutral';
}

// ─── 抓取單一指標並組成卡片 ───
async function buildCard(ind) {
  let points;
  if (ind.source === 'yahoo') {
    points = await fetchYahooSeries(ind.symbol, 'max');
  } else {
    // 回看年數拉長到約 26 年，讓走勢大圖能涵蓋 2000 網路泡沫、2008 金融海嘯等
    // 歷史事件（多數 FRED 序列起點更早，會自動回傳實際擁有的範圍）。
    // yoy 需多一年前置資料；季資料再多抓幾年確保換算完整。
    const yearsBack = ind.transform === 'yoy'
      ? (ind.periodsPerYear === 4 ? 30 : 27)
      : 26;
    points = await fetchFredSeries(ind.seriesId, isoDaysAgo(yearsBack * 365));
  }
  const metric = computeMetric(points, ind.transform, ind.periodsPerYear);
  if (metric.length === 0) throw new Error(`${ind.key} 換算後無資料`);

  const latest = metric[metric.length - 1];
  const prev = metric.length > 1 ? metric[metric.length - 2] : null;
  const value = latest.val;
  const delta = prev ? value - prev.val : NaN;
  const scale = Math.max(Math.abs(value), 1);
  const dir = dirOf(delta, scale);
  const cls = classify(ind, dir);

  const context = buildHistoricalContext(metric, ind, value);
  const tip = context
    ? { ...ind.tip, context }
    : ind.tip;

  const card = {
    key: ind.key,
    group: ind.group,
    label: ind.label,
    labelEn: ind.labelEn,
    value: formatValue(value, ind.format, ind.decimals),
    rawValue: value,
    change: formatChange(delta, ind.format, ind.decimals),
    dir,
    badge: dir === 'up' ? '上升' : dir === 'down' ? '下降' : '持平',
    badgeKind: cls.badgeKind,
    valueColorKey: cls.valueColorKey,
    changeColorKey: cls.changeColorKey,
    inverted: !!ind.inverted,
    good: cls.good,
    meaningful: cls.meaningful,
    spark: buildSparkline(metric),
    substitute: ind.substitute || null,
    tip,
    context,
    format: ind.format,
    decimals: ind.decimals ?? 2,
    asOf: latest.date,
  };
  return { card, metric };
}

// 把單一指標的格式化函式對外公開（供 /api/series 用）
export { formatValue };

// ─── 抓取全部指標（容錯：個別失敗不影響其他）───
export async function getIndicatorCards() {
  const results = await Promise.allSettled(INDICATORS.map((ind) => buildCard(ind)));
  const cards = {};
  const seriesHistory = {}; // key → 完整歷史序列 [{date,val}]
  const degraded = [];
  let missingKey = false;
  results.forEach((r, idx) => {
    const ind = INDICATORS[idx];
    if (r.status === 'fulfilled') {
      cards[ind.key] = r.value.card;
      seriesHistory[ind.key] = r.value.metric;
    } else {
      if (r.reason instanceof MissingKeyError) missingKey = true;
      degraded.push({ key: ind.key, label: ind.label, reason: String(r.reason?.message || r.reason) });
    }
  });
  // 只要偵測到金鑰未設定，就視為設定問題（畫面顯示設定教學），
  // 不因為少數免金鑰來源（如黃金）成功就誤判為正常。
  if (missingKey) {
    throw new MissingKeyError('尚未設定 FRED_API_KEY');
  }
  return { cards, seriesHistory, degraded };
}

// ─── 殖利率曲線（真實，跨天期）───
const CURVE_SERIES = [
  ['3M', 'DGS3MO'], ['6M', 'DGS6MO'], ['1Y', 'DGS1'], ['2Y', 'DGS2'],
  ['3Y', 'DGS3'], ['5Y', 'DGS5'], ['7Y', 'DGS7'], ['10Y', 'DGS10'],
  ['20Y', 'DGS20'], ['30Y', 'DGS30'],
];

export async function getYieldCurve() {
  const results = await Promise.allSettled(
    CURVE_SERIES.map(([, id]) => fetchFredSeries(id, isoDaysAgo(90)))
  );
  const maturities = [];
  const yields = [];
  const prevYields = [];
  results.forEach((r, i) => {
    if (r.status !== 'fulfilled' || r.value.length === 0) return;
    const pts = r.value;
    const last = pts[pts.length - 1].value;
    // 約一個月前（21 個交易日）
    const ago = pts[Math.max(0, pts.length - 22)].value;
    maturities.push(CURVE_SERIES[i][0]);
    yields.push(last);
    prevYields.push(ago);
  });
  const inverted = yields.length >= 2 && yields[0] > yields[yields.length - 1];
  return { maturities, yields, prevYields, inverted };
}
