import { describe, expect, it } from 'vitest';
import { BillPipeline } from '../src/domain/pipeline/billPipeline';
import { batchDedup, checkDuplicate, dedupAgainstHistory, dedupFingerprint, type DedupConfig } from '../src/domain/transaction/dedup';
import { recognizeTransfers } from '../src/domain/transaction/transferRecognizer';
import { parseLedger } from '../src/domain/core/ledger';
import type { Category } from '../src/domain/taxonomy/categories';
import type { ImportedEvent, Transaction } from '../src/domain/core/types';

const ledger = parseLedger([{ path: 'main.bean', content: '2026-01-01 open Assets:Alipay CNY\n2026-01-01 open Assets:WeChat CNY\n2026-01-01 open Assets:Bank:CMB CNY\n2026-01-01 open Expenses:Food CNY\n2026-01-01 open Expenses:Uncategorized CNY\n2026-01-01 open Income:Uncategorized CNY\n' }]);

const evt = (over: Partial<ImportedEvent> & { id: string }): ImportedEvent => {
  const { id = 'e1', ...rest } = over;
  return {
    occurredAt: '2026-02-01T10:00:00', amount: '-100', currency: 'CNY',
    direction: 'expense', counterparty: '', memo: '', raw: {},
    id,
    ...rest,
  };
};

describe('dedup - checkDuplicate', () => {
  const config: DedupConfig = { timeWindowMinutes: 5, checkAccountLink: true };

  it('时间完全相同 + 金额一致 → 高置信度重复', () => {
    const a = evt({ id: 'a' });
    const b = evt({ id: 'b' });
    const result = checkDuplicate(b, [a], config);
    expect(result.isDuplicate).toBe(true);
    expect(result.confidence).toBe('high');
  });

  it('时间窗口内 + 金额匹配 + 对手方一致 → 中置信度重复', () => {
    const a = evt({ id: 'a', occurredAt: '2026-02-01T10:00:00', counterparty: '星巴克' });
    const b = evt({ id: 'b', occurredAt: '2026-02-01T10:03:00', counterparty: '星巴克' });
    const result = checkDuplicate(b, [a], config);
    expect(result.isDuplicate).toBe(true);
    expect(result.confidence).toBe('medium');
  });

  it('时间窗口内 + 金额匹配 + 无对手方 → 低置信度重复', () => {
    const a = evt({ id: 'a', occurredAt: '2026-02-01T10:00:00' });
    const b = evt({ id: 'b', occurredAt: '2026-02-01T10:03:00' });
    const result = checkDuplicate(b, [a], config);
    expect(result.isDuplicate).toBe(true);
    expect(result.confidence).toBe('low');
  });

  it('时间超出窗口 → 非重复', () => {
    const a = evt({ id: 'a', occurredAt: '2026-02-01T10:00:00' });
    const b = evt({ id: 'b', occurredAt: '2026-02-01T10:10:00' });  // 10 分钟后
    const result = checkDuplicate(b, [a], config);
    expect(result.isDuplicate).toBe(false);
  });

  it('金额不同 → 非重复', () => {
    const a = evt({ id: 'a', amount: '100' });
    const b = evt({ id: 'b', amount: '200' });
    const result = checkDuplicate(b, [a], config);
    expect(result.isDuplicate).toBe(false);
  });
});

describe('dedup - batchDedup', () => {
  it('批量去重保留首个、丢弃重复', () => {
    const events = [
      evt({ id: 'a', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', occurredAt: '2026-02-01T10:00:00' }),  // 与 a 重复
      evt({ id: 'c', occurredAt: '2026-02-01T11:00:00' }),  // 不同时间
    ];
    const result = batchDedup(events);
    expect(result.accepted).toHaveLength(2);
    expect(result.duplicates).toHaveLength(1);
    expect(result.duplicates[0].id).toBe('b');
  });
});

describe('dedup - dedupAgainstHistory', () => {
  it('与已入账交易去重', () => {
    const history: Transaction[] = [{
      id: 't1', date: '2026-02-01T10:00:00', flag: '*',
      postings: [{ account: 'Assets:Alipay', amount: '-100', currency: 'CNY' }, { account: 'Expenses:Food', amount: '100', currency: 'CNY' }],
      tags: [], links: [], source: 'mobile.bean', raw: '',
    }];
    const events = [
      evt({ id: 'a', occurredAt: '2026-02-01T10:00:00' }),  // 与历史重复
      evt({ id: 'b', occurredAt: '2026-02-02T10:00:00' }),  // 新交易
    ];
    const result = dedupAgainstHistory(events, history);
    expect(result.accepted).toHaveLength(1);
    expect(result.duplicates).toHaveLength(1);
  });
});

describe('transferRecognizer - recognizeTransfers', () => {
  it('Income + Expend → Transfer（先于去重）', () => {
    const events = [
      evt({ id: 'a', amount: '100', direction: 'expense', counterparty: '支付宝', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', amount: '100', direction: 'income', counterparty: '银行卡', occurredAt: '2026-02-01T10:02:00' }),
    ];
    const result = recognizeTransfers(events);
    expect(result.transfers).toHaveLength(1);
    // 当没有 raw 数据时，resolveSourceAccount 返回 Assets:Unknown
    expect(result.transfers[0].draft.postings[0].account).toBe('Assets:Unknown');
    expect(result.transfers[0].draft.postings[1].account).toBe('Assets:Unknown');
    expect(result.remaining).toHaveLength(0);
  });

  it('相同对手方也会识别为转账（由金额和时间决定）', () => {
    const events = [
      evt({ id: 'a', amount: '100', direction: 'expense', counterparty: '支付宝', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', amount: '100', direction: 'income', counterparty: '支付宝', occurredAt: '2026-02-01T10:02:00' }),
    ];
    const result = recognizeTransfers(events);
    expect(result.transfers).toHaveLength(1);
    expect(result.remaining).toHaveLength(0);
  });

  it('金额不一致不配对', () => {
    const events = [
      evt({ id: 'a', amount: '100', direction: 'expense', counterparty: '支付宝' }),
      evt({ id: 'b', amount: '200', direction: 'income', counterparty: '银行卡' }),
    ];
    const result = recognizeTransfers(events);
    expect(result.transfers).toHaveLength(0);
  });

  it('超出时间窗口不配对', () => {
    const events = [
      evt({ id: 'a', amount: '100', direction: 'expense', counterparty: '支付宝', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', amount: '100', direction: 'income', counterparty: '银行卡', occurredAt: '2026-02-10T10:00:00' }),
    ];
    const result = recognizeTransfers(events);
    expect(result.transfers).toHaveLength(0);
  });

  it('金额格式不一致（包含多余的小数点/零）也应配对成功', () => {
    const events = [
      evt({ id: 'a', amount: '-100.00', direction: 'expense', counterparty: '支付宝', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', amount: '100', direction: 'income', counterparty: '银行卡', occurredAt: '2026-02-01T10:02:00' }),
    ];
    const result = recognizeTransfers(events);
    expect(result.transfers).toHaveLength(1);
    expect(result.remaining).toHaveLength(0);
  });
});

describe('BillPipeline - 责任链顺序', () => {
  it('转账识别先于去重：Income+Expend 合并为 1 条转账，而非丢弃重复', async () => {
    const pipeline = new BillPipeline();
    const events = [
      evt({ id: 'a', amount: '100', direction: 'expense', counterparty: '支付宝', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', amount: '100', direction: 'income', counterparty: '银行卡', occurredAt: '2026-02-01T10:02:00' }),
    ];
    const result = await pipeline.process(events, {
      ledger, rules: [], categories: [], history: [],
    });
    expect(result.drafts).toHaveLength(1);
    expect(result.drafts[0].isTransfer).toBe(true);
    expect(result.transferCount).toBe(1);
    expect(result.duplicates).toHaveLength(0);
  });

  it('重复事件被去重丢弃', async () => {
    const pipeline = new BillPipeline();
    const events = [
      evt({ id: 'a', amount: '50', direction: 'expense', counterparty: '超市', occurredAt: '2026-02-01T10:00:00' }),
      evt({ id: 'b', amount: '50', direction: 'expense', counterparty: '超市', occurredAt: '2026-02-01T10:01:00' }),
    ];
    const result = await pipeline.process(events, {
      ledger, rules: [], categories: [], history: [],
    });
    expect(result.drafts).toHaveLength(1);
    expect(result.duplicates).toHaveLength(1);
  });

  it('非转账非重复事件走分类', async () => {
    const categories: Category[] = [
      { id: 'food', name: '餐饮', type: 'expense', linkedAccount: 'Expenses:Food', keywords: ['餐', '饭'] },
    ];
    const pipeline = new BillPipeline();
    const events = [
      evt({ id: 'a', amount: '30', direction: 'expense', counterparty: '麦当劳餐厅', occurredAt: '2026-02-01T10:00:00' }),
    ];
    const result = await pipeline.process(events, {
      ledger, rules: [], categories, history: [],
    });
    expect(result.drafts).toHaveLength(1);
    expect(result.drafts[0].isTransfer).toBe(false);
    expect(result.drafts[0].draft.postings[1].account).toBe('Expenses:Food');  // 关键词"餐"命中
  });

  it('空事件列表返回空结果', async () => {
    const pipeline = new BillPipeline();
    const result = await pipeline.process([], {
      ledger, rules: [], categories: [], history: [],
    });
    expect(result.drafts).toHaveLength(0);
    expect(result.transferCount).toBe(0);
  });
});

describe('BillPipeline - 并发串行化', () => {
  it('并发 process 互斥，结果不交错', async () => {
    const pipeline = new BillPipeline();
    const batch1 = [evt({ id: 'a1', amount: '100', direction: 'expense', counterparty: '支付宝' })];
    const batch2 = [evt({ id: 'b1', amount: '200', direction: 'expense', counterparty: '微信' })];

    const [r1, r2] = await Promise.all([
      pipeline.process(batch1, { ledger, rules: [], categories: [], history: [] }),
      pipeline.process(batch2, { ledger, rules: [], categories: [], history: [] }),
    ]);

    expect(r1.drafts).toHaveLength(1);
    expect(r2.drafts).toHaveLength(1);
  });
});

describe('dedupFingerprint', () => {
  it('相同关键字段产生相同指纹', () => {
    const a = evt({ id: 'a', counterparty: 'X' });
    const b = evt({ id: 'b', counterparty: 'X' });
    expect(dedupFingerprint(a)).toBe(dedupFingerprint(b));
  });

  it('不同金额产生不同指纹', () => {
    const a = evt({ id: 'a', amount: '100', counterparty: 'X' });
    const b = evt({ id: 'b', amount: '200', counterparty: 'X' });
    expect(dedupFingerprint(a)).not.toBe(dedupFingerprint(b));
  });

  it('不同对手方产生不同指纹', () => {
    const a = evt({ id: 'a', counterparty: 'Shop A' });
    const b = evt({ id: 'b', counterparty: 'Shop B' });
    expect(dedupFingerprint(a)).not.toBe(dedupFingerprint(b));
  });
});
