import { addDecimals } from './decimal';
import type { Account, BalanceAssertion, Diagnostic, LedgerFile, LedgerIndex, Option, Posting, Transaction, TransactionDraft, ValidationResult } from './types';

/**
 * Beancount 解析器（增强版正则）。
 *
 * 覆盖率矩阵见 src/domain/grammar.md。不认识的高级指令记入 `unsupported`（只读保留），
 * 保证真实用户的复杂账本不会因个别指令解析失败而整体不可用。
 */

// ---- 指令正则 ----
const OPEN = /^(\d{4}-\d{2}-\d{2})\s+open\s+([^\s;]+)((?:\s+[A-Z][A-Z0-9_, -]*)*)/;
const CLOSE = /^(\d{4}-\d{2}-\d{2})\s+close\s+([^\s;]+)/;
const TXN = /^(\d{4}-\d{2}-\d{2})\s+([*!])(?:\s+"((?:[^"\\]|\\.)*)")?(?:\s+"((?:[^"\\]|\\.)*)")?/;
const BALANCE = /^(\d{4}-\d{2}-\d{2})\s+balance\s+([^\s;]+)\s+(-?[\d,]+(?:\.\d+)?)\s+([A-Z][A-Z0-9_-]*)?/;
const OPTION = /^option\s+"([^"]+)"\s+"((?:[^"\\]|\\.)*)"/;
const _PAD = /^(\d{4}-\d{2}-\d{2})\s+pad\s+([^\s]+)\s+([^\s]+)/;
const _NOTE = /^(\d{4}-\d{2}-\d{2})\s+note\s+([^\s]+)\s+"((?:[^"\\]|\\.)*)"/;
const _DATE_DIRECTIVE = /^\d{4}-\d{2}-\d{2}\s+(commodity|price|event|document|query|custom|location|plugin)\b/;
// posting：账户 + 可选金额/币种 + 可选 cost {...} + 可选 price @
// 分组说明：
//   [1] 账户名（如 Assets:Cash:Wallet）
//   [2] 可选金额（如 -100.50 或 1,234.56）
//   [3] 可选币种（如 CNY、USD）
//   [4] 剩余部分（cost/price/注释等，由后续逻辑解析）
const POSTING = /^\s+([^\s;]+)(?:\s+(-?[\d,]+(?:\.\d+)?))?(?:\s+([A-Z][A-Z0-9_-]*))?(.*)$/;
// posting 行的 metadata：  key: "value"
const _POSTING_META = /^\s+([A-Za-z_][\w-]*):\s*"([^"]*)"/;
// 交易级/指令级 metadata（紧随 header 后的缩进行，非 posting）
const META = /^\s+([A-Za-z_][\w-]*):\s*"([^"]*)"/;

/** FNV-1a 32-bit hash，用于 id 生成与 checksum。非加密用途，碰撞率足够低。 */
export const hash = (text: string): string => {
  let h = 2166136261;
  for (const c of text) h = Math.imul(h ^ c.charCodeAt(0), 16777619);
  return (h >>> 0).toString(16);
};

const parseMetaLine = (line: string): [string, string] | null => {
  const m = line.match(META);
  return m ? [m[1], m[2]] : null;
};

const SUPPORTED_DIRECTIVE = /^\d{4}-\d{2}-\d{2}\s+(open|close|balance)\b/;

export interface ParseLedgerOptions {
  /** 是否解析 include 指令（默认 false，由调用方处理多文件）。 */
  resolveIncludes?: boolean;
}

export function parseLedger(files: LedgerFile[], options?: ParseLedgerOptions): LedgerIndex {
  const resolveIncludes = options?.resolveIncludes ?? false;
  const accounts = new Map<string, Account>();
  const transactions: Transaction[] = [];
  const balances: BalanceAssertion[] = [];
  const parsedOptions: Option[] = [];
  const diagnostics: Diagnostic[] = [];
  const unsupported: Diagnostic[] = [];

  // 构建文件查找表，用于解析 include 指令（仅 resolveIncludes=true 时使用）
  const fileMap = new Map<string, LedgerFile>();
  if (resolveIncludes) {
    for (const f of files) {
      const basename = f.path.split(/[/\\]/).pop() ?? f.path;
      fileMap.set(basename, f);
      fileMap.set(f.path, f);
    }
  }

  /** 解析 include 指令引用的文件内容。递归深度限制 10 防止循环引用。 */
  const resolveInclude = (includePath: string, depth = 0): string => {
    if (depth > 10) return '';
    const included = fileMap.get(includePath);
    if (!included) return '';
    const lines = included.content.replace(/\r\n/g, '\n').split('\n');
    const resolved: string[] = [];
    for (const l of lines) {
      const includeMatch = l.match(/^include\s+"([^"]+)"/);
      if (includeMatch) {
        resolved.push(resolveInclude(includeMatch[1], depth + 1));
      } else {
        resolved.push(l);
      }
    }
    return resolved.join('\n');
  };

  for (const file of files) {
    const lines = file.content.replace(/\r\n/g, '\n').split('\n');
    let index = 0;
    while (index < lines.length) {
      const line = lines[index];
      const trimmed = line.trim();

      // 空行与注释跳过
      if (trimmed === '' || trimmed.startsWith(';')) {
        index++;
        continue;
      }

      // option 指令（无日期前缀）
      const opt = line.match(OPTION);
      if (opt) {
        parsedOptions.push({ key: opt[1], value: opt[2], source: file.path, line: index + 1 });
        index++;
        continue;
      }

      // plugin 指令
      if (/^plugin\s+"/.test(line)) {
        unsupported.push({ level: 'warning', file: file.path, line: index + 1, message: `plugin 指令以只读兼容模式保留` });
        index++;
        continue;
      }

      const open = line.match(OPEN);
      if (open) {
        const currencies = (open[3] || '').trim().split(/\s+/).filter(Boolean);
        accounts.set(open[2], { name: open[2], openedOn: open[1], currencies });
        index++;
        continue;
      }

      const close = line.match(CLOSE);
      if (close) {
        const current = accounts.get(close[2]);
        if (current) current.closedOn = close[1];
        else diagnostics.push({ level: 'warning', file: file.path, line: index + 1, message: `关闭了未开户账户 ${close[2]}` });
        index++;
        continue;
      }

      const bal = line.match(BALANCE);
      if (bal) {
        balances.push({ date: bal[1], account: bal[2], amount: bal[3].replace(/,/g, ''), currency: bal[4], source: file.path, line: index + 1 });
        index++;
        continue;
      }

      // 其他日期型指令（commodity/price/event/document/query/custom/pad/note）
      if (/^\d{4}-\d{2}-\d{2}\s/.test(line) && !TXN.test(line) && !SUPPORTED_DIRECTIVE.test(line)) {
        const kind = line.match(/^\d{4}-\d{2}-\d{2}\s+(\w+)/)?.[1] ?? 'unknown';
        unsupported.push({ level: 'warning', file: file.path, line: index + 1, message: `${kind} 指令以只读兼容模式保留` });
        // pad/note 这类可能带后续缩进行，一并跳过
        index++;
        while (index < lines.length && (/^\s/.test(lines[index]) && lines[index].trim() !== '')) index++;
        continue;
      }

      // 交易
      const start = line.match(TXN);
      if (start) {
        const rawLines = [line];
        const postings: Posting[] = [];
        const metadata: Record<string, string> = {};
        const tags = [...line.matchAll(/#([\w-]+)/g)].map(m => m[1]);
        const links = [...line.matchAll(/\^([\w-]+)/g)].map(m => m[1]);
        let cursor = index + 1;
        let sawPosting = false;
        while (cursor < lines.length && (/^\s/.test(lines[cursor]) || lines[cursor] === '')) {
          const sub = lines[cursor];
          rawLines.push(sub);
          if (sub.trim() === '' || sub.trim().startsWith(';')) { cursor++; continue; }

          // 先尝试 posting（账户名 + 可选金额）
          const posting = sub.match(POSTING);
          const meta = parseMetaLine(sub);
          if (posting && posting[1] && !posting[1].startsWith(';') && posting[2]) {
            // 有金额 → posting
            const p: Posting = {
              account: posting[1],
              amount: posting[2].replace(/,/g, ''),
              currency: posting[3],
            };
            // 解析尾部 cost {...} / price @
            const tail = posting[4] || '';
            const costMatch = tail.match(/\{([^}]+)\}/);
            if (costMatch) p.cost = costMatch[1].trim();
            const priceMatch = tail.match(/@\s*(-?[\d,]+(?:\.\d+)?)/);
            if (priceMatch) p.price = priceMatch[1].replace(/,/g, '');
            postings.push(p);
            sawPosting = true;
          } else if (posting && posting[1] && !posting[1].startsWith(';') && !meta) {
            // 无金额的 posting（如自动平衡的 elided leg）
            postings.push({ account: posting[1] });
            sawPosting = true;
          } else if (meta) {
            // metadata 行：归属到交易级（若未出现 posting）或上一个 posting（若已出现）
            if (sawPosting && postings.length > 0) {
              const last = postings[postings.length - 1];
              last.metadata = { ...(last.metadata || {}), [meta[0]]: meta[1] };
            } else {
              metadata[meta[0]] = meta[1];
            }
          }
          cursor++;
        }
        const raw = rawLines.join('\n');
        transactions.push({
          id: metadata['dr-id'] || hash(`${file.path}:${cursor}:${raw}`),
          date: start[1], flag: start[2],
          payee: start[4] ? start[3] : undefined,
          narration: start[4] ?? start[3] ?? '',
          postings, tags, links, source: file.path, raw,
          ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
        });
        index = cursor;
        continue;
      }

      // include 指令：解析引用文件并合并内容
      const includeMatch = line.match(/^include\s+"([^"]+)"/);
      if (includeMatch) {
        const includedContent = resolveInclude(includeMatch[1]);
        if (includedContent) {
          const includedFile: LedgerFile = { path: includeMatch[1], content: includedContent };
          const includedIndex = parseLedger([includedFile], { resolveIncludes });
          for (const [name, acc] of includedIndex.accounts) accounts.set(name, acc);
          transactions.push(...includedIndex.transactions);
          balances.push(...includedIndex.balances);
          parsedOptions.push(...includedIndex.options);
          diagnostics.push(...includedIndex.diagnostics);
          unsupported.push(...includedIndex.unsupported);
        }
        index++;
        continue;
      }

      // 未识别行
      index++;
    }
  }

  return { files, accounts, transactions, balances, options: parsedOptions, diagnostics, unsupported };
}

function isZeroBalance(s: string): boolean { return s === '0' || s === '-0'; }

export function validateTransaction(draft: TransactionDraft, ledger: LedgerIndex): ValidationResult {
  const errors: string[] = [];
  const grouped = new Map<string, string[]>();
  if (!/^\d{4}-\d{2}-\d{2}$/.test(draft.date)) errors.push('日期必须为 YYYY-MM-DD');
  if (draft.postings.length < 2) errors.push('交易至少需要两条分录');

  let emptyAmountCount = 0;
  for (const posting of draft.postings) {
    const account = ledger.accounts.get(posting.account);
    if (!account) errors.push(`账户未 open：${posting.account}`);
    else if (account.closedOn && draft.date > account.closedOn) errors.push(`账户已关闭：${posting.account}`);
    
    if (posting.amount && posting.currency) {
      grouped.set(posting.currency, [...(grouped.get(posting.currency) ?? []), posting.amount]);
    } else {
      emptyAmountCount++;
    }
  }

  const balances: Record<string, string> = {};
  if (emptyAmountCount === 0) {
    for (const [currency, amounts] of grouped) {
      balances[currency] = addDecimals(amounts);
      if (!isZeroBalance(balances[currency])) errors.push(`${currency} 不平衡：${balances[currency]}`);
    }
  } else if (emptyAmountCount > 1) {
    errors.push('交易中最多只能有一条分录省略金额');
  }

  return { valid: errors.length === 0, errors, balances };
}


/** 生成唯一交易标识（时间戳 + 随机，嵌入 metadata）。 */
export function generateDrId(): string {
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}

export function serializeTransaction(draft: TransactionDraft): string {
  const flag = draft.flag || '*';
  const meta = { ...draft.metadata };
  if (!meta['dr-id']) meta['dr-id'] = generateDrId();
  const metaLines = Object.entries(meta).map(([k, v]) => `  ${k}: "${v}"`).join('\n') + '\n';
  const tagsStr = (draft.tags ?? []).map(t => ` #${t}`).join('');
  const linksStr = (draft.links ?? []).map(l => ` ^${l}`).join('');
  const header = `${draft.date} ${flag}${draft.payee ? ` "${draft.payee}"` : ''} "${draft.narration}"${tagsStr}${linksStr}`;

  // 构建 posting 行（含 posting 级 metadata），避免 splice 的脆弱性
  const allPostingLines: string[] = [];
  for (const p of draft.postings) {
    let line = `  ${p.account}`;
    if (p.amount) {
      line += `  ${p.amount}`;
      if (p.currency) line += ` ${p.currency}`;
    }
    if (p.cost) line += ` {${p.cost}}`;
    if (p.price) line += ` @ ${p.price}`;
    allPostingLines.push(line);
    // posting 级 metadata 紧跟在 posting 行后
    if (p.metadata) {
      for (const [k, v] of Object.entries(p.metadata)) {
        allPostingLines.push(`  ${k}: "${v}"`);
      }
    }
  }

  return `${header}\n${metaLines}${allPostingLines.join('\n')}\n`;
}

/** 计算包含 balance 断言在内的各账户实时余额。 */
export function computeAccountBalances(ledger: LedgerIndex): Map<string, string> {
  const balances = new Map<string, string>();

  // 1. 找出每个账户最晚的 balance 余额断言
  const latestBalances = new Map<string, { amount: string; date: string }>();
  for (const b of ledger.balances) {
    const existing = latestBalances.get(b.account);
    if (!existing || b.date > existing.date) {
      latestBalances.set(b.account, { amount: b.amount, date: b.date });
    }
  }

  // 2. 初始化为断言值
  for (const [account, info] of latestBalances) {
    balances.set(account, info.amount);
  }

  // 3. 累加交易（仅对有断言的账户做日期过滤，无断言的账户从 0 累加所有交易）
  for (const t of ledger.transactions) {
    for (const p of t.postings) {
      if (!p.amount) continue;

      const assertion = latestBalances.get(p.account);
      if (assertion && t.date < assertion.date) continue;

      balances.set(p.account, addDecimals([balances.get(p.account) ?? '0', p.amount]));
    }
  }

  return balances;
}

/**
 * 从账本的 option 指令读取默认币种（operating_currency）。
 * 找不到时回退到 CNY。
 */
export function getDefaultCurrency(ledger: LedgerIndex | null): string {
  if (!ledger) return 'CNY';
  const oc = ledger.options.find(o => o.key === 'operating_currency');
  return oc?.value || 'CNY';
}
