import { describe, expect, it } from 'vitest';
import { MockMaintenanceFs, MockMaintenanceDb, scanOrphanFiles, cleanOrphanFiles, runMaintenance } from '../src/services/data/maintenance';
import { AiVisionProcessor, MockAiVisionProcessor } from '../src/services/ocr/AiVisionProcessor';
import { setupDeepLinking, parseDeepLink } from '../src/services/deepLink';
import { generateRemark } from '../src/domain/rules/remarkTemplate';
import type { ImportedEvent } from '../src/domain/core/types';

const evt = (over: Partial<ImportedEvent> = {}): ImportedEvent => ({
  id: 'e1', occurredAt: '2026-07-13T14:30:00', amount: '100', currency: 'CNY', direction: 'expense',
  counterparty: '星巴克', memo: '拿铁', raw: { fee: '0.5', tags: 'coffee' },
  ...over,
});

// ============ maintenance ============

describe('maintenance', () => {
  it('scanOrphanFiles 扫描孤立附件', async () => {
    const fs = new MockMaintenanceFs();
    fs.setFile('/attachments/receipt1.jpg', 1024);  // 被引用
    fs.setFile('/attachments/orphan.png', 512);      // 孤立
    fs.setFile('/thumbnails/thumb1.png', 256);       // 缩略图
    const orphans = await scanOrphanFiles(
      fs,
      new Set(['/attachments/receipt1.jpg']),
      { attachments: '/attachments', thumbnails: '/thumbnails', cache: '/cache' },
    );
    expect(orphans).toHaveLength(2);  // orphan.png + thumb1.png
    expect(orphans.some(o => o.path === '/attachments/orphan.png')).toBe(true);
  });

  it('cleanOrphanFiles 删除孤立文件', async () => {
    const fs = new MockMaintenanceFs();
    fs.setFile('/attachments/orphan.png', 512);
    const orphans = [{ type: 'attachment' as const, path: '/attachments/orphan.png', size: 512 }];
    const cleaned = await cleanOrphanFiles(fs, orphans);
    expect(cleaned).toBe(1);
    expect(fs.deleted).toContain('/attachments/orphan.png');
  });

  it('runMaintenance 完整维护', async () => {
    const fs = new MockMaintenanceFs();
    fs.setFile('/cache/tmp.txt', 100);
    const db = new MockMaintenanceDb();
    db.issues = ['测试问题'];
    const report = await runMaintenance(
      fs, db, new Set(),
      { attachments: '/attachments', thumbnails: '/thumbnails', cache: '/cache' },
    );
    expect(report.cleanedCount).toBe(1);  // 清理了 cache/tmp.txt
    expect(db.vacuumed).toBe(true);
    expect(report.dbIssues).toEqual(['测试问题']);
  });
});

// ============ AiVisionProcessor ============

describe('AiVisionProcessor', () => {
  it('未启用时返回 null', async () => {
    const processor = new AiVisionProcessor({ enabled: false, apiUrl: '', apiKey: '', model: '' });
    expect(await processor.recognizeBill('img')).toBeNull();
  });

  it('Mock 返回预设结果', async () => {
    const mock = new MockAiVisionProcessor({
      id: 'ai1', occurredAt: '2026-07-13', amount: '50', currency: 'CNY', direction: 'expense',
      counterparty: '测试', memo: '', raw: {},
    });
    const result = await mock.recognizeBill('img');
    expect(result?.amount).toBe('50');
  });

  it('Mock setResponse 切换结果', async () => {
    const mock = new MockAiVisionProcessor(null);
    expect(await mock.recognizeBill('img')).toBeNull();
    mock.setResponse({ id: 'ai2', occurredAt: 'now', amount: '30', currency: 'CNY', direction: 'income', counterparty: '', memo: '', raw: {} });
    expect((await mock.recognizeBill('img'))?.direction).toBe('income');
  });
});

// ============ deepLink setupDeepLinking ============

describe('deepLink setupDeepLinking', () => {
  it('返回清理函数（测试环境降级）', () => {
    const cleanup = setupDeepLinking();
    expect(typeof cleanup).toBe('function');
    cleanup();  // 不抛错
  });

  it('注入 handler 被调用', () => {
    const actions: string[] = [];
    const cleanup = setupDeepLinking((action) => actions.push(action.type));
    // 测试环境无 Linking，handler 不会被调用，但 setup 不报错
    cleanup();
    expect(actions).toHaveLength(0);
  });

  it('parseDeepLink 仍可用', () => {
    expect(parseDeepLink('beanmobile://tab/home')?.type).toBe('open-tab');
  });
});

// ============ remarkTemplate 16 占位符 ============

describe('remarkTemplate 完整占位符', () => {
  it('商品名称占位符', () => {
    const remark = generateRemark('【商品名称】', evt({ memo: '拿铁咖啡' }));
    expect(remark).toBe('拿铁咖啡');
  });

  it('货币类型占位符', () => {
    expect(generateRemark('【货币类型】', evt())).toBe('CNY');
  });

  it('手续费占位符', () => {
    expect(generateRemark('手续费【手续费】', evt())).toBe('手续费0.5');
  });

  it('标签占位符', () => {
    expect(generateRemark('标签：【标签】', evt())).toContain('coffee');
  });

  it('交易类型占位符', () => {
    expect(generateRemark('【交易类型】', evt())).toBe('expense');
  });

  it('多占位符组合', () => {
    const remark = generateRemark('【商户名称】【金额】【货币类型】', evt());
    expect(remark).toBe('星巴克100CNY');
  });
});
