import axios from 'axios';
import { warn } from './log.js';

/** 本次執行的 token 累計（跨所有 LLM 呼叫）。 */
const runUsage = { calls: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };

/**
 * 安全數字轉換：將任意輸入轉為有限數字，無法轉換或非有限值（NaN／Infinity）一律回 0。
 * 常用於正規化外部 API 回應或 HTTP header 取出的值，避免污染後續加總與百分比運算。
 * @param {*} x 任意待轉換的值。
 * @returns {number} 有限數字；否則為 0。
 */
function num(x) {
  const n = Number(x);
  return Number.isFinite(n) ? n : 0;
}

/**
 * 把各平台回應中的 token usage 正規化成 { promptTokens, completionTokens, totalTokens }。
 * 支援：OpenAI 相容 usage、OpenAI Responses（input/output_tokens）、
 * Gemini usageMetadata、Ollama 原生 eval_count、OpenCode tokens。
 * 回應中沒有任何可辨識的 usage 時回傳 null。
 */
export function extractUsage(data) {
  if (!data || typeof data !== 'object') return null;

  // OpenAI 相容 / OpenAI Responses
  const u = data.usage;
  if (u && typeof u === 'object') {
    const prompt = num(u.prompt_tokens ?? u.input_tokens);
    const completion = num(u.completion_tokens ?? u.output_tokens);
    const total = u.total_tokens != null ? num(u.total_tokens) : prompt + completion;
    if (prompt || completion || total) return { promptTokens: prompt, completionTokens: completion, totalTokens: total };
  }

  // Gemini 原生 usageMetadata
  const g = data.usageMetadata;
  if (g && typeof g === 'object') {
    const prompt = num(g.promptTokenCount);
    const completion = num(g.candidatesTokenCount);
    const total = g.totalTokenCount != null ? num(g.totalTokenCount) : prompt + completion;
    if (prompt || completion || total) return { promptTokens: prompt, completionTokens: completion, totalTokens: total };
  }

  // Ollama 原生回應
  if (data.prompt_eval_count != null || data.eval_count != null) {
    const prompt = num(data.prompt_eval_count);
    const completion = num(data.eval_count);
    return { promptTokens: prompt, completionTokens: completion, totalTokens: prompt + completion };
  }

  // OpenCode（tokens 可能位於 data.tokens 或 data.info.tokens）
  const t = data.tokens || data.info?.tokens || data.data?.info?.tokens;
  if (t && typeof t === 'object') {
    const prompt = num(t.input ?? t.prompt);
    const completion = num(t.output ?? t.completion);
    const total = t.total != null ? num(t.total) : prompt + completion;
    if (prompt || completion || total) return { promptTokens: prompt, completionTokens: completion, totalTokens: total };
  }

  return null;
}

/** 記錄一次 LLM 呼叫的 usage（無法解析時仍計一次呼叫，但 token 計 0）。 */
export function recordUsage(data) {
  runUsage.calls += 1;
  const u = extractUsage(data);
  if (u) {
    runUsage.promptTokens += u.promptTokens;
    runUsage.completionTokens += u.completionTokens;
    runUsage.totalTokens += u.totalTokens;
  }
  return u;
}

/** 取得本次執行至今的 token 累計（複本）。 */
export function getRunUsage() {
  return { ...runUsage };
}

/** 重置累計（測試用）。 */
export function resetRunUsage() {
  runUsage.calls = 0;
  runUsage.promptTokens = 0;
  runUsage.completionTokens = 0;
  runUsage.totalTokens = 0;
}

/** 最近一次回應的速率配額（rate limit）快照，用來計算「當前視窗剩餘百分比」。 */
const rateLimit = { hasData: false, remaining: null, limit: null, kind: null };

/**
 * 將物件第一層的 key 全部轉為小寫並回傳新物件，方便對大小寫不敏感的 HTTP header 取值。
 * 不修改傳入物件；僅處理第一層 key。呼叫端須自行確保傳入為物件。
 * @param {Object<string, *>} obj 來源物件（通常為 HTTP response headers）。
 * @returns {Object<string, *>} key 全小寫的新物件。
 */
function lowerCaseKeys(obj) {
  const out = {};
  for (const k of Object.keys(obj)) out[k.toLowerCase()] = obj[k];
  return out;
}

/**
 * 從回應 header 擷取速率配額剩餘量／上限。
 * 支援 OpenAI 相容（x-ratelimit-*-tokens）與 Anthropic（anthropic-ratelimit-tokens-*），
 * 兩者皆缺時退而採用 requests 維度。記錄「最近一次」的數值（即最新的視窗狀態）。
 */
export function recordRateLimit(headers) {
  if (!headers || typeof headers !== 'object') return;
  const h = lowerCaseKeys(headers);

  let remaining = h['x-ratelimit-remaining-tokens'] ?? h['anthropic-ratelimit-tokens-remaining'];
  let limit = h['x-ratelimit-limit-tokens'] ?? h['anthropic-ratelimit-tokens-limit'];
  let kind = 'tokens';
  if (remaining == null || limit == null) {
    remaining = h['x-ratelimit-remaining-requests'] ?? h['anthropic-ratelimit-requests-remaining'];
    limit = h['x-ratelimit-limit-requests'] ?? h['anthropic-ratelimit-requests-limit'];
    kind = 'requests';
  }
  if (remaining == null || limit == null) return;

  rateLimit.hasData = true;
  rateLimit.remaining = num(remaining);
  rateLimit.limit = num(limit);
  rateLimit.kind = kind;
}

/** 取得最近一次的速率配額快照（複本）。 */
export function getRateLimit() {
  return { ...rateLimit };
}

/** 重置速率配額快照（測試用）。 */
export function resetRateLimit() {
  rateLimit.hasData = false;
  rateLimit.remaining = null;
  rateLimit.limit = null;
  rateLimit.kind = null;
}

/**
 * 去除字串結尾的單一斜線（常用於正規化 baseURL 以利串接路徑）。
 * 空值會被視為空字串；僅移除最後一個斜線，不處理連續尾斜線。
 * @param {*} s 來源字串（通常為 URL）。
 * @returns {string} 去除結尾斜線後的字串。
 */
const stripSlash = (s) => String(s || '').replace(/\/$/, '');

/**
 * 以解析後的 hostname 精確比對 baseURL 是否為 OpenRouter（僅接受 apex 域名 openrouter.ai）。
 * 用於 fetchAccountQuota 的安全守門：防止被偽造或子網域 baseURL 矇騙而外洩 API key。
 * 無法解析為合法 URL 時回 false（不丟例外）。
 * @param {string} baseURL 待驗證的 base URL。
 * @returns {boolean} hostname 恰為 openrouter.ai 時為 true，否則 false。
 */
function isOpenRouterBaseURL(baseURL) {
  try {
    return new URL(baseURL).hostname.toLowerCase() === 'openrouter.ai';
  } catch {
    return false;
  }
}

/**
 * 呼叫 OpenRouter GET /auth/key，以 API key 取得帳號額度（單位 USD credits）。
 * remaining 缺漏時以 limit - used 推算；limit 為 null（無上限）時 remaining 亦為 null。
 * 本函式不攔截例外；HTTP／網路錯誤會向上拋出，由呼叫端負責降級。
 * @param {{apiKey:string, baseURL:string}} cfg 連線設定（API key 與 base URL）。
 * @param {function(string, object): Promise<{data:*}>} get HTTP GET 函式（可注入，預設 axios.get）。
 * @returns {Promise<{available:true, used:number, limit:number|null, remaining:number|null, currency:'USD', source:'openrouter'}>}
 *   額度資訊。
 * @throws {Error} HTTP 請求失敗（逾時、非 2xx、網路錯誤等）時拋出。
 */
async function fetchOpenRouterQuota({ apiKey, baseURL }, get) {
  const resp = await get(`${stripSlash(baseURL)}/auth/key`, {
    headers: { Authorization: `Bearer ${apiKey}` },
    timeout: 30000,
  });
  const d = resp.data?.data || {};
  const used = num(d.usage);
  const limit = d.limit == null ? null : num(d.limit);
  const remaining = d.limit_remaining == null ? (limit == null ? null : limit - used) : num(d.limit_remaining);
  return { available: true, used, limit, remaining, currency: 'USD', source: 'openrouter' };
}

/**
 * 各平台帳號額度查詢策略。
 * 多數官方平台無法僅憑 API key 取得帳號額度（需 org/admin 權限），故誠實回報「無法取得」並附原因；
 * 本地/自架服務（ollama/opencode）則回報「不適用」。
 */
const QUOTA_STRATEGIES = {
  openai: async (cfg, get) => {
    if (isOpenRouterBaseURL(cfg.baseURL)) return fetchOpenRouterQuota(cfg, get);
    return { available: false, reason: 'OpenAI 帳號額度需 dashboard session 權限，API key 無法取得' };
  },
  claude: async () => ({ available: false, reason: 'Anthropic 額度需 Admin API 權限，一般 API key 無法取得' }),
  antigravity: async () => ({ available: false, reason: 'Antigravity 額度由 Google 帳務/方案管理，CLI 無法直接查詢' }),
  gemini: async () => ({ available: false, reason: 'Gemini 額度由 Google Cloud quota 管理，API key 無法直接查詢' }),
  amazonq: async () => ({ available: false, reason: 'Amazon Q 額度由 AWS 帳務管理，需 AWS 憑證查詢' }),
  ollama: async () => ({ available: false, reason: '本地服務，無帳號額度概念' }),
  opencode: async () => ({ available: false, reason: '自架服務，無帳號額度概念' }),
};

/**
 * 取得指定平台的帳號額度。任何失敗都降級為 { available: false, reason }，不丟例外。
 * deps.get 可注入以利測試（預設 axios.get）。
 */
export async function fetchAccountQuota(provider, config = {}, deps = {}) {
  const get = deps.get || axios.get;
  const strategy = QUOTA_STRATEGIES[provider];
  if (!strategy) return { available: false, reason: `未支援 ${provider} 額度查詢` };
  const apiKey = Array.isArray(config.apiKeys) ? config.apiKeys[0] : config.apiKey;
  try {
    return await strategy({ apiKey, baseURL: config.baseURL }, get);
  } catch (e) {
    warn(`取得 ${provider} 帳號額度失敗（視為無法取得）: ${e.message}`);
    return { available: false, reason: e.message };
  }
}

/**
 * 將數字格式化為千分位字串（保留原有小數）。null／undefined／NaN 一律回 '0'。
 * @param {number|string|null|undefined} n 待格式化的數值。
 * @returns {string} 千分位字串，例如 1234567 → '1,234,567'。
 */
function fmt(n) {
  if (n == null || Number.isNaN(Number(n))) return '0';
  const [int, frac] = String(Number(n)).split('.');
  const withCommas = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  return frac ? `${withCommas}.${frac}` : withCommas;
}

/**
 * 將數值格式化為金額字串：有幣別時前綴幣別（如 'USD 1,234'），無幣別則只回千分位數字。
 * @param {string} currency 幣別代碼（空字串／falsy 表示無幣別）。
 * @param {number|string|null|undefined} n 數值。
 * @returns {string} 格式化後的金額字串。
 */
function money(currency, n) {
  return currency ? `${currency} ${fmt(n)}` : fmt(n);
}

/**
 * 四捨五入到小數一位（用於百分比顯示）。
 * 不對非數字防呆；非有限輸入會得到 NaN（呼叫端應先確保為有限數）。
 * @param {number|string} n 數值。
 * @returns {number} 四捨五入到一位小數的結果。
 */
function round1(n) {
  return Math.round(Number(n) * 10) / 10;
}

const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };

/**
 * 計算剩餘百分比（remaining / limit × 100），四捨五入到一位小數。
 * 任一參數為 null／undefined／NaN／Infinity，或 limit ≤ 0 時回 null，
 * 以避免算出 Infinity%、NaN%、負百分比或除以零。
 * @param {number|null|undefined} remaining 剩餘量。
 * @param {number|null|undefined} limit 上限。
 * @returns {number|null} 百分比（一位小數）；無法計算時為 null。
 */
function calculatePercent(remaining, limit) {
  if (remaining == null || limit == null) return null;
  const rem = Number(remaining);
  const lim = Number(limit);
  if (!Number.isFinite(rem) || !Number.isFinite(lim) || lim <= 0) return null;
  return round1((rem / lim) * 100);
}

/**
 * 計算「剩餘可用百分比」，依優先序擇一：
 * 1. 帳號額度（quota 有有效上限）→ 剩餘 credits / 上限；
 * 2. 速率配額（rate limit header，有有效上限）→ 當前視窗剩餘 / 上限；
 * 上限或剩餘為無效值（null／0／負數／NaN／Infinity）時跳過計算，落到 { percent: null, reason }。
 */
export function resolveRemainingPercent(quota, rate) {
  if (quota?.available && quota.limit != null) {
    const limit = Number(quota.limit);
    const remaining = quota.remaining == null ? limit - num(quota.used) : Number(quota.remaining);
    const percent = calculatePercent(remaining, limit);
    if (percent != null) {
      return { percent, basis: '帳號額度', remaining, limit, unit: quota.currency || '' };
    }
  }
  if (rate?.hasData) {
    const percent = calculatePercent(rate.remaining, rate.limit);
    if (percent != null) {
      const kindLabel = RATE_KIND_LABEL[rate.kind] || rate.kind;
      return { percent, basis: `速率配額（當前視窗，${kindLabel}）`, remaining: Number(rate.remaining), limit: Number(rate.limit), unit: '' };
    }
  }
  let reason;
  if (quota?.available && quota.limit == null) reason = '帳號額度無上限，無法計算百分比';
  else if (quota && !quota.available) reason = quota.reason || '平台未提供額度';
  else reason = '平台未提供額度或速率配額資訊';
  return { percent: null, reason };
}

/**
 * 把 resolveRemainingPercent 的結果格式化為一行 Markdown 文字（剩餘可用百分比與明細）。
 * 無法計算時輸出帶原因的說明字串。
 * @param {{percent:number, basis:string, remaining:number, limit:number, unit:string}|{percent:null, reason:string}} pct
 *   resolveRemainingPercent 的回傳值。
 * @returns {string} 單行 Markdown 字串。
 */
function remainingLine(pct) {
  if (pct.percent == null) return `剩餘可用：無法計算百分比（${pct.reason}）`;
  const detail = `${pct.basis}：${money(pct.unit, pct.remaining)} / ${money(pct.unit, pct.limit)}`;
  return `剩餘可用 **${pct.percent}%**（${detail}）`;
}

/** 產生 PR Review 本文用的「AI 助理使用量」Markdown 區塊。 */
export function formatUsageStats(provider, model, usage, quota, rate) {
  const pct = resolveRemainingPercent(quota, rate);
  const lines = [
    '## 🤖 AI 助理使用量',
    '',
    `**本次審查**（${provider} / ${model}，共 ${usage.calls} 次呼叫）`,
    '',
    '| 提示 token | 回應 token | 合計 |',
    '| --- | --- | --- |',
    `| ${fmt(usage.promptTokens)} | ${fmt(usage.completionTokens)} | ${fmt(usage.totalTokens)} |`,
    '',
    '**剩餘可用**',
    '',
    remainingLine(pct),
  ];
  return lines.join('\n');
}

/** 產生單行 log 用的使用量摘要。 */
export function formatUsageStatsLine(provider, model, usage, quota, rate) {
  const pct = resolveRemainingPercent(quota, rate);
  const tokenPart = `本次 ${provider}/${model}: 提示${usage.promptTokens} + 回應${usage.completionTokens} = ${usage.totalTokens} token（${usage.calls} 次呼叫）`;
  const pctPart = pct.percent == null
    ? `；剩餘可用: 無法計算（${pct.reason}）`
    : `；剩餘可用: ${pct.percent}%（${pct.basis} ${money(pct.unit, pct.remaining)}/${money(pct.unit, pct.limit)}）`;
  return tokenPart + pctPart;
}
