/**
 * AI 功能抽象（plan.md「Phase 8 AI 功能增强」+「8.0 AI Provider 抽象层」）。
 *
 * 参考 AutoAccounting 的 BaseOpenAIProvider（8 个 Provider 共用 OpenAI 兼容协议）+ Gemini 独立实现。
 * 本模块定义接口与纯逻辑（prompt 构建/JSON 解析/脱敏/去思考链），
 * 网络请求通过注入的 fetch 抽象，使可单测。
 */

import type { ImportedEvent, LedgerIndex } from './core/types';
import type { Category } from './taxonomy/categories';

export interface ChatMessage {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

export interface AiProviderConfig {
  id: string;
  name: string;
  apiKey: string;
  baseUrl: string;
  model: string;
}

/** AI Provider 抽象。 */
export interface AiProvider {
  readonly config: AiProviderConfig;
  chat(messages: ChatMessage[]): Promise<string>;
}

/** 账单识别结果。 */
export interface AiBillResult {
  type: 'income' | 'expense' | 'transfer';
  amount: string;
  currency: string;
  counterparty: string;
  narration: string;
  time: string;
}

// ============ Provider 实现 ============

/** OpenAI 兼容协议（DeepSeek/Kimi/智谱/OpenRouter/QWen/硅基流动/MiMo/ChatGPT 共用）。 */
export abstract class BaseOpenAIProvider implements AiProvider {
  constructor(public readonly config: AiProviderConfig) {}
  protected fetchImpl: (url: string, init: RequestInit) => Promise<Response> = fetch;

  async chat(messages: ChatMessage[]): Promise<string> {
    const response = await this.fetchImpl(`${this.config.baseUrl}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.config.apiKey}`,
      },
      body: JSON.stringify({ model: this.config.model, messages, temperature: 0.1 }),
    });
    if (!response.ok) throw new Error(`AI 请求失败: ${response.status}`);
    const data = await response.json() as { choices: { message: { content: string } }[] };
    if (!data.choices?.length) throw new Error('AI 响应无有效选择');
    return data.choices[0].message.content;
  }
}

// ============ 纯逻辑工具 ============

/** 去除思考链（DeepSeek-R1 等模型的 <think>...</think>）。 */
export function removeThink(text: string): string {
  return text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
}

/** 隐私脱敏（plan.md「8.0 隐私脱敏」）。 */
export function sanitizeForAi(text: string): string {
  return text
    .replace(/\b\d{17}(\d|X)\b(?![\d])/g, '[身份证]') // 身份证（恰好18位，必须在银行卡号之前匹配）
    .replace(/\b\d{16,19}\b/g, '[卡号]')           // 银行卡号
    .replace(/1[3-9]\d{9}/g, '[手机号]')             // 手机号
    .replace(/\b\d{6}\b/g, (match, offset, str) => {
      const ctx = String(str).slice(Math.max(0, offset - 20), offset + 20);
      return /验证码|验证|code/i.test(ctx) ? '[验证码]' : match;
    });
}

/** 从 AI 响应中解析 JSON（容错）。 */
export function parseAiJson<T>(response: string): T | null {
  const cleaned = removeThink(response);
  // 尝试提取 ```json ... ``` 或裸 JSON
  const jsonMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/) ?? cleaned.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
  if (!jsonMatch) return null;
  try {
    return JSON.parse(jsonMatch[1].trim()) as T;
  } catch {
    return null;
  }
}

/** 构建账单识别 prompt（plan.md「8.0 Prompt 工程」）。 */
export function buildBillRecognitionPrompt(ocrText: string, categories: Category[]): ChatMessage[] {
  const categoryList = categories.map(c => `${c.name}(${c.type})`).join('、');
  return [
    {
      role: 'system',
      content: `你是一个账单识别助手。从用户输入中提取账单信息，返回 JSON 数组。
字段：type(income/expense/transfer)、amount(数字)、currency(默认CNY)、counterparty、narration(≤15字)、time(ISO8601)。
可用分类：${categoryList}。非账单内容返回空数组 []。只返回 JSON，不要解释。`,
    },
    { role: 'user', content: sanitizeForAi(ocrText) },
  ];
}

/** 自然语言记账（plan.md「8.1」）。 */
export function buildNaturalLanguagePrompt(input: string): ChatMessage[] {
  return [
    {
      role: 'system',
      content: `你是一个记账助手。解析用户输入为交易 JSON：{type,amount,currency,account,counterparty,narration,tags[]}。
无法解析为交易时，返回纯文本建议。可解析时只返回 JSON。`,
    },
    { role: 'user', content: sanitizeForAi(input) },
  ];
}

/** 月度总结 prompt（plan.md「8.3」）。 */
export function buildMonthlySummaryPrompt(stats: {
  totalExpense: string; totalIncome: string; transactionCount: number;
  topCategories: { category: string; amount: string }[];
}): ChatMessage[] {
  return [
    {
      role: 'system',
      content: '请根据数据生成简洁的中文月度财务总结，给出消费习惯分析和理财建议。',
    },
    {
      role: 'user',
      content: `总支出: ${stats.totalExpense}\n总收入: ${stats.totalIncome}\n交易笔数: ${stats.transactionCount}\n主要支出: ${stats.topCategories.map(c => `${c.category}:${c.amount}`).join(', ')}`,
    },
  ];
}

/** 分类识别 prompt（plan.md「8.4」）。 */
export function buildCategoryRecognitionPrompt(event: ImportedEvent, categories: Category[]): ChatMessage[] {
  const categoryList = categories.map(c => `${c.name}(${c.type})`).join('、');
  return [
    { role: 'system', content: `判断交易最合适的分类。可用分类：${categoryList}。只返回分类名称。` },
    { role: 'user', content: sanitizeForAi(`${event.counterparty} ${event.amount}元 ${event.memo}`) },
  ];
}

/** 资产映射 prompt（plan.md「8.5」）。 */
export function buildAssetMappingPrompt(counterparty: string, accounts: string[]): ChatMessage[] {
  return [
    { role: 'system', content: `判断最合适的资产账户。可用账户：${accounts.join('、')}。只返回账户名称。` },
    { role: 'user', content: sanitizeForAi(`对方: ${counterparty}`) },
  ];
}

// ============ 高阶函数（注入 provider） ============

/** 自然语言记账（注入 provider）。 */
export async function processNaturalLanguage(
  input: string,
  provider: AiProvider,
): Promise<{ type: 'draft'; draft: AiBillResult } | { type: 'text'; text: string }> {
  const messages = buildNaturalLanguagePrompt(input);
  const response = await provider.chat(messages);
  const parsed = parseAiJson<AiBillResult>(response);
  if (parsed && parsed.amount && parsed.type) {
    return { type: 'draft', draft: parsed };
  }
  return { type: 'text', text: removeThink(response) };
}

/** AI 分类识别（注入 provider）。 */
export async function aiRecognizeCategory(
  event: ImportedEvent,
  categories: Category[],
  provider: AiProvider,
): Promise<Category | null> {
  const response = await provider.chat(buildCategoryRecognitionPrompt(event, categories));
  const name = removeThink(response).trim();
  return categories.find(c => c.name === name) ?? null;
}

/** AI 资产映射（注入 provider）。 */
export async function aiMapAsset(
  counterparty: string,
  ledger: LedgerIndex,
  provider: AiProvider,
): Promise<string | null> {
  const accounts = [...ledger.accounts.keys()].filter(a =>
    !a.startsWith('Income') && !a.startsWith('Expenses') && !a.startsWith('Equity')
  );
  const response = await provider.chat(buildAssetMappingPrompt(counterparty, accounts));
  const name = removeThink(response).trim();
  return accounts.find(a => a === name) ?? null;
}
