/**
 * OCR 自动记账 - Layer 1 规则匹配与账单解析（plan.md「3.3 Layer 1」+「3.x BillParser」）。
 *
 * 参考 AutoAccounting 的 RuleMatcher（微信支付/支付宝/银行卡正则）+ BillParser。
 * 本模块为纯函数，不依赖原生，可单测；输入是 OCR 识别文本，输出是 ImportedEvent。
 */

import { detectDirection, PAYMENT_PACKAGE_SET } from '../core/constants';
import { OCR_RULES, DETAIL_PAGE_LABELS, DETAIL_PAGE_MIN_HITS, type OcrRulePattern } from '../rules/ruleConfig';
import { negateDecimal } from '../core/decimal';
import type { Direction, ImportedEvent } from '../core/types';

/** 规则匹配结果。 */
export interface OcrRuleMatch {
  matched: boolean;
  event: ImportedEvent | null;
  /** 命中的规则名（微信/支付宝/银行卡/未知）。 */
  ruleName: string;
}

/** OCR 规则类型（从 ruleConfig 导入）。 */
type OcrRule = OcrRulePattern;

// 参考 AutoAccounting RuleMatcher 的正则模式
const RULES: OcrRule[] = [
  {
    name: '微信支付',
    pattern: /微信支付/,
    extractors: {
      amount: /(?:金额|支付金额|￥|¥)\s*(-?[\d,]+(?:\.\d+)?)/,
      merchant: /(?:商户|收款方|对方)\s*[:：]?\s*([^\n,，。]+?)(?=\s*(?:金额|支付金额|￥|¥|元|支出|收入|付款|收款|退款|消费|转账|尾号|\d{4}[-/年]|\d{1,2}:)|$)/,
      time: /((?:\d{4}[-/年])?\d{1,2}[-/月]\d{1,2}[日]?[\s]+\d{1,2}:\d{2}(?::\d{2})?)/,
    },
  },
  {
    name: '支付宝',
    pattern: /支付宝|alipay/i,
    extractors: {
      amount: /(?:金额|支付金额|￥|¥)\s*(-?[\d,]+(?:\.\d+)?)/,
      merchant: /(?:收款方|对方|商户)\s*[:：]?\s*([^\n,，。]+?)(?=\s*(?:金额|支付金额|￥|¥|元|支出|收入|付款|收款|退款|消费|转账|尾号|\d{4}[-/年]|\d{1,2}:)|$)/,
      time: /((?:\d{4}[-/年])?\d{1,2}[-/月]\d{1,2}[日]?[\s]+\d{1,2}:\d{2}(?::\d{2})?)/,
    },
  },
  {
    name: '银行卡',
    pattern: /尾号\d{4}|信用卡|储蓄卡|借记卡/,
    extractors: {
      amount: /(?:消费|支出|金额|￥|¥)\s*(-?[\d,]+(?:\.\d+)?)/,
      merchant: /(?:商户|对方|于)\s*[:：]?\s*([^\n,，。]+?)(?=\s*(?:金额|支付金额|￥|¥|元|支出|收入|付款|收款|退款|消费|转账|尾号|\d{4}[-/年]|\d{1,2}:)|$)/,
      time: /((?:\d{4}[-/年])?\d{1,2}[-/月]\d{1,2}[日]?[\s]+\d{1,2}:\d{2}(?::\d{2})?)/,
      card: /尾号\s*(\d{4})/,
    },
  },
];

function extract(pattern: RegExp | undefined, text: string): string {
  if (!pattern) return '';
  return pattern.exec(text)?.[1] ?? '';
}

/**
 * Layer 1 规则匹配（plan.md「3.3」）。
 * 快速、无成本：对 OCR 文本跑预定义正则，命中即提取。
 */
export function matchOcrRule(text: string, packageName?: string): OcrRuleMatch {
  for (const rule of RULES) {
    if (!rule.pattern.test(text)) continue;

    const amountRaw = extract(rule.extractors.amount, text);
    if (!amountRaw) continue;  // 无金额不算账单

    const merchant = extract(rule.extractors.merchant, text).trim();
    const time = extract(rule.extractors.time, text);
    const card = extract(rule.extractors.card, text);
    const direction = detectDirection(text);

    // 过滤掉千分位逗号，取绝对值后根据方向决定符号（修复：避免已带负号时双重取反）
    const cleanAmount = amountRaw.replace(/,/g, '').replace(/^-/, '');
    const amount = direction === 'expense' ? negateDecimal(cleanAmount) : cleanAmount;

    const event: ImportedEvent = {
      id: `ocr:${rule.name}:${time || Date.now()}:${amountRaw}`,
      occurredAt: time ? normalizeTime(time) : new Date().toISOString().slice(0, 16),
      amount,
      currency: 'CNY',
      direction,
      counterparty: merchant,
      memo: card ? `银行卡尾号${card}` : rule.name,
      raw: { text, packageName: packageName ?? '' },
    };
    // 复合信息合并：主规则命中后，额外检查银行卡尾号（如微信+银行卡组合支付）
    if (!card) {
      const cardFallback = /尾号\s*(\d{4})/.exec(text);
      if (cardFallback) {
        event.memo = `银行卡尾号${cardFallback[1]}`;
        event.raw['card'] = cardFallback[1];
      }
    }
    return { matched: true, event, ruleName: rule.name };
  }
  return { matched: false, event: null, ruleName: '未匹配' };
}

/** 规范化时间格式（支持 2026-07-13 14:30 / 2026/7/13 14:30 / 2026年7月13日 14:30 / 7-13 14:30 缺失年份）。 */
function normalizeTime(time: string): string {
  let normalized = time
    .replace(/[年]/g, '-')
    .replace(/[月]/g, '-')
    .replace(/[日]/g, '')
    .replace(/\//g, '-')
    .trim();

  // 处理缺失年份的情况 (例如 "07-13 14:30" 变为 "2026-07-13 14:30")
  if (/^\d{1,2}-\d{1,2}/.test(normalized)) {
    // 智能推断年份：如果补当前年后日期在未来超过 1 天，说明是去年的账单
    const now = new Date();
    const candidate = new Date(`${now.getFullYear()}-${normalized}`);
    const year = candidate.getTime() - now.getTime() > 86400000 ? now.getFullYear() - 1 : now.getFullYear();
    normalized = `${year}-${normalized}`;
  }

  // 规范化月份和日期 (例如 2026-7-13 -> 2026-07-13, 2026-7-1 -> 2026-07-01)
  return normalized
    .replace(/-(\d)(?=-)/g, '-0$1')    // 月补零（后面还有 -）
    .replace(/-(\d)(?=\s|$)/g, '-0$1') // 日补零（后面是空格或结尾）
    .trim();
}

/**
 * 支付宝/微信「账单详情页」结构化标签词。
 *
 * 详情页（非支付弹窗）布局是「标签 | 值」形式，金额常裸露成独立段（如 | -8.51 |），
 * 没有 ￥/元 前缀，现有规则和退化解析都覆盖不到。这里用特征词计数判定是否为详情页。
 */
// 详情页特征词已迁移至 rules/ruleConfig.ts（集中管理）。

/** 详情页判定阈值：命中特征词数 ≥ 此值才认为是账单详情页。 */
// 详情页最小命中数已迁移至 rules/ruleConfig.ts（阈值从 2 提升至 3，避免列表页误判）。

/** 判断是否为账单详情页（特征词命中数 ≥ 2）。 */
export function checkIsDetailPage(text: string): boolean {
  const hits = DETAIL_PAGE_LABELS.reduce((n, label) => text.includes(label) ? n + 1 : n, 0);
  return hits >= DETAIL_PAGE_MIN_HITS;
}

/**
 * 账单详情页金额提取。
 *
 * 策略：自上而下逐段扫描，排除日期/时间/订单号行后，提取第一个满足以下条件之一的金额段：
 *   1. 带有小数位（格式 XX.XX），正负号可选，且对 OCR 错识的分隔符（如 - , _ 等）自动纠错；
 *   2. 无小数位，但必须带有正负号前缀（如 +7540），防止抓错页面上其他的普通整型数字。
 */
function extractDetailPageAmount(text: string): string {
  const segments = text.split(/[|\n]/).map(s => s.trim());
  for (const seg of segments) {
    if (/\d{4}[-./]\d{1,2}[-./]\d{1,2}/.test(seg)) continue;  // 日期行
    if (/\d{1,2}:\d{2}/.test(seg)) continue;                    // 时间行
    if (/\d{12,}/.test(seg)) continue;                           // 长订单号

    // OCR 纠错 1：前导符号归一化——OCR 常把负号识别为 en-dash(– U+2013)、em-dash(— U+2014)、
    // 数字减号(− U+2212)、全角减号(－) 等，统一归一化为半角减号 -，避免金额段匹配失败。
    // （如 "–18.56" → "-18.56"）
    const signNormalized = seg.replace(/^[\u2010-\u2015\u2212\uFF0D]/, '-');

    // OCR 纠错 2：将形如 898-34、898,34、898_34 等带错识的分隔符的金额段规范化为带小数点的格式
    const normalized = signNormalized.replace(/(\d+)[-–—_~,，.\s]+(\d{2})$/, '$1.$2');

    // 1. 小数金额正则匹配（带符号或不带符号，如 898.34 或 -0.58，支持千分位点号如 1.898.34）
    const mDec = normalized.match(/^([+-]?[\d,.]+\.\d{2})$/);
    if (mDec) return mDec[1];

    // 2. 整数金额正则匹配（必须带有正负号，如 +7540 或 -50，以避免误抓年份、卡尾号等）
    const mInt = normalized.match(/^([+-][\d,]+)$/);
    if (mInt) return mInt[1];
  }
  return '';
}

/** 规范化详情页时间（支持 2026-07-15.09:01:18、2026年7月13日20:29:46 等各种常见时间格式）。 */
function normalizeDetailTime(raw: string): string {
  let s = raw
    // 清洗"延迟扣费"等描述性前缀（云闪付延迟扣款：时间值常粘连"延迟扣费|2026年6月27日 13:30:15"）
    // 去掉"|"分隔符前的非日期文字，保留从年份/数字开始的日期时间主体
    .replace(/^[^|\d]*[|]?\s*/, '')
    .replace(/^(?:延迟扣费|延迟扣款|即时扣费|即时扣款)\s*/, '')
    .replace(/年/g, '-')
    .replace(/月/g, '-')
    .replace(/日\s*/g, ' ') // 将「日」或带空格的「日 」合并规范化为一个空格
    .replace(/\//g, '-')
    .trim();

  // 如果没有前导四位数字年份（如 "07-15 20:13:14"），补上当前年份
  if (/^\d{1,2}-\d{1,2}/.test(s) && !/^\d{4}/.test(s)) {
    const now = new Date();
    const candidate = new Date(`${now.getFullYear()}-${s}`);
    const year = candidate.getTime() - now.getTime() > 86400000 ? now.getFullYear() - 1 : now.getFullYear();
    s = `${year}-${s}`;
  }

  // 针对可能缺失空格的 "YYYY-MM-DDHH:mm" 进行修复，如 "2026-7-1320:29:46" -> "2026-7-13 20:29:46"
  s = s.replace(/^(\d{4}-\d{1,2}-\d{1,2})(\d{2}:\d{2}(?::\d{2})?)$/, '$1 $2');

  // 将连接符/换行符/T 等换成统一空格，例如 2026-07-15.09:01:18 → 2026-07-15 09:01:18
  s = s.replace(/^(\d{4}-\d{1,2}-\d{1,2})[.T\s]+(\d{1,2}:\d{2})(?::\d{2})?.*$/, '$1 $2');

  // 补零逻辑：例如 2026-7-13 20:29 -> 2026-07-13 20:29
  const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})/);
  if (m) {
    const year = m[1];
    const month = m[2].padStart(2, '0');
    const day = m[3].padStart(2, '0');
    const hour = m[4].padStart(2, '0');
    const min = m[5];
    return `${year}-${month}-${day} ${hour}:${min}`;
  }

  // 纯日期补零（无时分，如 2026-6-27 -> 2026-06-27）
  const mDate = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
  if (mDate) {
    return `${mDate[1]}-${mDate[2].padStart(2, '0')}-${mDate[3].padStart(2, '0')}`;
  }

  return s;
}

/**
 * 解析账单详情页（支付宝/微信账单明细）。
 *
 * 与退化解析的区别：详情页是结构化标签布局，有「付款方式」「支付时间」「商品说明」等字段，
 * 金额裸露无 ￥/元 前缀。本函数通过特征词判定 + 专用金额/字段提取器处理。
 *
 * @returns 命中详情页则返回 ImportedEvent，否则返回 null（交由后续退化解析处理）
 */
function parseDetailPageBill(text: string, packageName?: string): ImportedEvent | null {
  // 特征词计数判定
  const hits = DETAIL_PAGE_LABELS.reduce((n, label) => text.includes(label) ? n + 1 : n, 0);
  if (hits < DETAIL_PAGE_MIN_HITS) return null;

  const amountRaw = extractDetailPageAmount(text);
  if (!amountRaw) return null;

  // 提取付款方式（供 resolveSourceAccount 解析资金来源账户）
  // 分隔符支持 |、：、:、空格（详情页标签与值之间可能是任意一种）
  // 标签兼容：支付宝/银行用「付款方式」，微信账单详情页用「支付方式」
  const method = extract(/(?:付款方式|支付方式)\s*[|：:\s]\s*([^\n|，,]+)/, text).trim();
  // 提取支付时间
  // 标签兼容：支付宝/微信「支付时间/创建时间/完成时间」；银行/云闪付「交易时间/扣款时间」；
  // 抖音月付「还款时间」
  // 值捕获组匹配"日期[可选换行/空格/.]时间"整体，兼容 - 和 年月日 两种日期格式，
  // 避免被换行符截断丢失时分（云闪付延迟扣款场景）。
  // 云闪付时间值常粘连"延迟扣费"前缀（如"延迟扣费2026年6月27日"），故允许前导非数字描述。
  const timeRaw = extract(/(?:支付时间|交易时间|创建时间|完成时间|扣款时间|还款时间)\s*[|：:\s]\s*(?:延迟扣费|延迟扣款|即时扣费|即时扣款)?\s*(\d{4}[-/年]\d{1,2}[-/月]\d{1,2}[日]?[\s\n.T]*(?:\d{1,2}:\d{2}(?::\d{2})?)?)/, text).trim();
  const occurredAt = timeRaw ? normalizeDetailTime(timeRaw) : new Date().toISOString().slice(0, 16);
  // 提取商品说明/商户名称（供分类匹配），支持带空格的商户/商品说明
  const subjectRaw = extract(/(?:商品说明|商品名称|商品)\s*[|：:\s]\s*([^\n|，,]+)/, text).trim();
  const payeeRaw = extract(/(?:对方账户|收款全称|收款商户|收款人|收款账户|商户全称|收款方|商户)\s*[|：:\s]\s*([^\n|，,]+)/, text).trim();
  
  let counterparty = subjectRaw || payeeRaw;
  if (counterparty) {
    // 清理 -订单编号xxxxxxxx 等交易流水号后缀，保留纯净商户名
    counterparty = counterparty.replace(/-(?:订单编号|订单号|交易号|流水号|订单|\d+).*$/i, '').trim();
  }

  // 云闪付兜底：无标准商户标签，按"商户名公司(乘车码)"模式提取（含全/半角括号）
  // OCR 文本常含 "长沙公交智慧大数据科技有限公司(乘车码)" 这种裸露商户名
  if (!counterparty && packageName === 'com.unionpay') {
    const unionPayMerchant = extract(/([\u4e00-\u9fa5]{2,}(?:有限公司|股份公司|中心|店|站|服务部)[（(]?[^）)]*[）)]?)/, text).trim();
    if (unionPayMerchant) counterparty = unionPayMerchant.replace(/（/g, '(').replace(/）/g, ')');
  }

  // 金额符号决定方向：负号=支出，正号=收入/退款；无符号按 detectDirection 推断
  const hasSign = /^[+-]/.test(amountRaw);
  const direction: Direction = hasSign
    ? (amountRaw.startsWith('-') ? 'expense' : 'income')
    : detectDirection(text);
  
  let absAmount = amountRaw.replace(/^[+-]/, '');
  const lastDotIdx = absAmount.lastIndexOf('.');
  if (lastDotIdx !== -1) {
    const integerPart = absAmount.slice(0, lastDotIdx).replace(/[,.]/g, '');
    const decimalPart = absAmount.slice(lastDotIdx + 1);
    absAmount = `${integerPart}.${decimalPart}`;
  } else {
    absAmount = absAmount.replace(/[,.]/g, '');
  }

  const raw: Record<string, string> = { text, packageName: packageName ?? '' };
  if (method) raw['收/付款方式'] = method;

  return {
    id: `ocr:detail:${Date.now()}:${absAmount}`,
    occurredAt,
    amount: direction === 'expense' || direction === 'fee' ? `-${absAmount}` : absAmount,
    currency: 'CNY',
    direction,
    counterparty: counterparty || '',
    memo: method ? `付款方式:${method}` : '账单详情页',
    raw,
  };
}

/**
 * BillParser（plan.md「3.x」）：解析 OCR 全文本为账单事件。
 * 比 RuleMatcher 更宽松——尝试从任意文本中提取金额与方向。
 */
export function parseOcrBill(text: string, packageName?: string): ImportedEvent | null {
  // 云闪付等详情页型 App：优先走详情页解析。
  // 原因：Layer 1 的"银行卡"规则会因文本含"信用卡"字样（如"招商银行信用卡[1589]"）误匹配，
  // 抢在详情页解析前返回错误的订单金额（¥1.40）而非实付金额（-1.16）。
  // 详情页解析器能精确取裸露的实付金额，并提取商户名。
  if (packageName === 'com.unionpay') {
    const detail = parseDetailPageBill(text, packageName);
    if (detail) return detail;
  }

  // 1. 先尝试规则匹配（支付弹窗等已知格式）
  const ruleMatch = matchOcrRule(text, packageName);
  if (ruleMatch.matched && ruleMatch.event) return ruleMatch.event;

  // 2. 账单详情页（结构化标签布局，金额裸露无 ￥/元 前缀）
  const detail = parseDetailPageBill(text, packageName);
  if (detail) return detail;

  // 如果是在内建支付应用（如支付宝/微信等）中截图，未匹配上规则和结构化详情页，
  // 则说明大概率是交易列表页、搜索页、理财页等，直接拦截防止退化解析误匹配
  if (packageName && PAYMENT_PACKAGE_SET.has(packageName)) {
    return null;
  }

  // 3. 退化：提取首个金额 + 推断方向，支持千分位逗号
  const amountMatch = text.match(/(?:￥|¥|金额|支付金额|扣款|消费|支付|支出|收入|退款|付款)\s*([\d,]+(?:\.\d+)?)/) ?? text.match(/([\d,]+(?:\.\d+)?)\s*(?:元)/);
  if (!amountMatch) return null;

  const amountStr = amountMatch[1].replace(/,/g, '');
  const direction = detectDirection(text);
  return {
    id: `ocr:fallback:${Date.now()}:${amountMatch[1]}`,
    occurredAt: new Date().toISOString().slice(0, 16),
    amount: direction === 'expense' ? negateDecimal(amountStr) : amountStr,
    currency: 'CNY',
    direction,
    counterparty: '',
    memo: 'OCR 退化解析',
    raw: { text, packageName: packageName ?? '' },
  };
}

/**
 * 可疑金额启发式：检测 OCR 可能丢小数点的金额（如 ¥143.97 误识为 ￥14397）。
 *
 * 触发条件（需同时满足，避免误报正常大额整数消费）：
 * 1. 识别金额为整数（无小数点）且绝对值 ≥ 阈值（典型 OCR 丢小数点特征）
 * 2. OCR 文本含外币/跨境特征（暗示原金额可能带小数，如信用卡外币交易）
 *
 * @param amount 识别出的金额字符串（可能带前导 -）
 * @param ocrText 原始 OCR 文本
 * @returns 命中时返回原因描述，未命中返回 null
 */
export function detectSuspiciousAmount(amount: string, ocrText: string): { reason: string } | null {
  const absAmount = amount.replace(/^-/, '').replace(/,/g, '');
  // 条件 1：整数金额（无小数点）且 ≥ 1000
  if (!/^\d+$/.test(absAmount)) return null;
  const value = parseInt(absAmount, 10);
  if (value < 1000) return null;
  // 条件 2：外币/跨境特征词
  const FOREIGN_FX_PATTERN = /交易地金额|交易地|外币|国家或地区|美元|欧元|日元|港币|英镑|汇率|\d+\.\d+\s*(?:USD|EUR|JPY|HKD|GBP)/;
  if (!FOREIGN_FX_PATTERN.test(ocrText)) return null;
  return {
    reason: `金额 ${absAmount} 疑似丢失小数点（OCR 常见误差），且文本含外币/跨境交易特征，请仔细核对`,
  };
}

