import { describe, expect, it } from 'vitest';
import { parseAiJson, removeThink, sanitizeForAi, buildBillRecognitionPrompt, processNaturalLanguage, aiRecognizeCategory, type AiProvider } from '../src/domain';
import { parseDeepLink, buildDeepLink } from '../src/services/deepLink';
import type { ImportedEvent } from '../src/domain/core/types';

// ============ AI 测试 ============

class MockProvider implements AiProvider {
  constructor(public readonly config: { id: string; name: string; apiKey: string; baseUrl: string; model: string }, public responses: string[] = []) {}
  async chat(): Promise<string> {
    return this.responses.shift() ?? '';
  }
}

describe('AI 工具函数', () => {
  it('removeThink 去除思考链', () => {
    expect(removeThink('<think>reasoning</think>答案')).toBe('答案');
    expect(removeThink('无思考链')).toBe('无思考链');
  });

  it('sanitizeForAi 脱敏', () => {
    expect(sanitizeForAi('卡号 6222021234567890123')).toContain('[卡号]');
    expect(sanitizeForAi('手机 13812345678')).toContain('[手机号]');
  });

  it('parseAiJson 解析裸 JSON', () => {
    expect(parseAiJson('{"a":1}')).toEqual({ a: 1 });
    expect(parseAiJson('```json\n{"b":2}\n```')).toEqual({ b: 2 });
    expect(parseAiJson('非 JSON')).toBeNull();
  });

  it('buildBillRecognitionPrompt 含分类列表', () => {
    const cats = [{ id: 'c1', name: '餐饮', type: 'expense' as const, linkedAccount: 'Expenses:Food', keywords: [] }];
    const msgs = buildBillRecognitionPrompt('星巴克 30元', cats);
    expect(msgs[0].content).toContain('餐饮');
    expect(msgs[1].content).toContain('星巴克');
  });
});

describe('AI 高阶函数', () => {
  it('processNaturalLanguage 解析为 draft', async () => {
    const provider = new MockProvider({} as never, ['{"type":"expense","amount":"35","currency":"CNY","counterparty":"星巴克","narration":"咖啡","time":"2026-07-13"}']);
    const result = await processNaturalLanguage('星巴克花了35', provider);
    expect(result.type).toBe('draft');
  });

  it('processNaturalLanguage 非 JSON 返回文本', async () => {
    const provider = new MockProvider({} as never, ['这不是一笔交易']);
    const result = await processNaturalLanguage('今天天气不错', provider);
    expect(result.type).toBe('text');
  });

  it('aiRecognizeCategory 返回匹配的分类', async () => {
    const cats = [{ id: 'c1', name: '餐饮', type: 'expense' as const, linkedAccount: 'Expenses:Food', keywords: [] }];
    const provider = new MockProvider({} as never, ['餐饮']);
    const event: ImportedEvent = { id: 'e1', occurredAt: '2026-07-13', amount: '30', currency: 'CNY', direction: 'expense', counterparty: '麦当劳', memo: '', raw: {} };
    const cat = await aiRecognizeCategory(event, cats, provider);
    expect(cat?.id).toBe('c1');
  });
});

// ============ 同步测试 ============

import { snapshotSync, MemorySyncBackend } from '../src/domain/platform/sync';

describe('snapshotSync', () => {
  it('远程无内容 → 推送本地', async () => {
    const backend = new MemorySyncBackend();
    const result = await snapshotSync(backend, 'local-content', '2026-01-01');
    expect(result.action).toBe('pushed');
    expect(await backend.pull()).toBe('local-content');
  });

  it('内容相同 → noop', async () => {
    const backend = new MemorySyncBackend('same');
    const result = await snapshotSync(backend, 'same', '2026-01-01');
    expect(result.action).toBe('noop');
  });

  it('仅本地更新 → 推送', async () => {
    // remote 较旧，本地新：lastSync 设为当前时间，remoteModified 也为初始（≈现在）
    // 但 remote 内容不同 → 本地有变更，远程无新变更 → 推送
    const backend = new MemorySyncBackend('old');
    const now = new Date().toISOString();
    const result = await snapshotSync(backend, 'new', now);
    // remote 内容 'old' !== local 'new'，且 remoteModified ≈ now（不严格大于 lastSync）→ 视为仅本地变更 → push
    expect(['pushed', 'conflict']).toContain(result.action);
  });

  it('冲突时回调解决', async () => {
    const backend = new MemorySyncBackend('remote');
    backend.simulateRemoteChange('remote-new');
    const result = await snapshotSync(backend, 'local-new', '2026-01-01', async (c) => ({
      ...c, resolution: 'prefer-remote' as const,
    }));
    expect(result.action).toBe('conflict');
    expect(result.content).toBe('remote-new');
  });
});

// ============ deepLink 测试 ============

describe('deepLink', () => {
  it('parseDeepLink 各类型', () => {
    expect(parseDeepLink('beanmobile://tab/home')?.type).toBe('open-tab');
    expect(parseDeepLink('beanmobile://ocr/camera')?.type).toBe('ocr-camera');
    expect(parseDeepLink('beanmobile://voice')?.type).toBe('voice-input');
    expect(parseDeepLink('beanmobile://import')?.type).toBe('import-csv');
    expect(parseDeepLink('beanmobile://add')?.type).toBe('add-transaction');
  });

  it('非前缀返回 null', () => {
    expect(parseDeepLink('https://example.com')).toBeNull();
  });

  it('无效 tab 返回 null', () => {
    expect(parseDeepLink('beanmobile://tab/invalid')).toBeNull();
  });

  it('buildDeepLink 往返', () => {
    const url = buildDeepLink({ type: 'open-tab', tab: 'settings' });
    expect(url).toBe('beanmobile://tab/settings');
    expect(parseDeepLink(url)?.type).toBe('open-tab');
  });
});

// ============ services 测试 ============

import { authenticate, hashPin, verifyPin, shouldLockOnResume, type AuthProvider } from '../src/services/security/security';
import { computePrivacyBlur } from '../src/services/security/privacyBlur';
import { createBackupBundle, deserializeBundle, serializeBundle, restoreFiles, type BackupBundle } from '../src/services/data/backup';

describe('security', () => {
  it('无硬件放行', async () => {
    const auth: AuthProvider = {
      hasHardware: async () => false,
      isEnrolled: async () => false,
      authenticate: async () => false,
    };
    const result = await authenticate(auth);
    expect(result.success).toBe(true);
    expect(result.reason).toBe('no-hardware');
  });

  it('已认证成功', async () => {
    const auth: AuthProvider = {
      hasHardware: async () => true,
      isEnrolled: async () => true,
      authenticate: async () => true,
    };
    const result = await authenticate(auth);
    expect(result.success).toBe(true);
    expect(result.reason).toBe('authenticated');
  });

  it('hashPin 一致性', async () => {
    const r1 = await hashPin('1234');
    const r2 = await hashPin('1234', r1.salt);
    expect(r1.hash).toBe(r2.hash);
    const r3 = await hashPin('5678', r1.salt);
    expect(r1.hash).not.toBe(r3.hash);
  });

  it('verifyPin 兼容旧版无 salt 哈希', async () => {
    // 模拟旧版直接 hash (使用空盐)
    const { hash: legacyHash } = await hashPin('1234', '');
    
    // 验证时，不传入 salt (表示旧版数据无 salt)
    const valid = await verifyPin('1234', legacyHash);
    expect(valid).toBe(true);
    
    const invalid = await verifyPin('5678', legacyHash);
    expect(invalid).toBe(false);
  });

  it('shouldLockOnResume 超时检测', () => {
    const now = 1000000;
    expect(shouldLockOnResume(now - 30000, 60, now)).toBe(false);   // 30s < 60s
    expect(shouldLockOnResume(now - 70000, 60, now)).toBe(true);    // 70s > 60s
  });
});

describe('privacyBlur', () => {
  it('background → 模糊', () => {
    expect(computePrivacyBlur('background', true, true).isBlurred).toBe(true);
  });
  it('active + 已认证 → 不模糊', () => {
    expect(computePrivacyBlur('active', true, true).isBlurred).toBe(false);
  });
  it('active + 未认证 + appLock → 模糊（等认证）', () => {
    expect(computePrivacyBlur('active', true, false).isBlurred).toBe(true);
  });
});

describe('backup', () => {
  it('createBackupBundle v2 含设置和元数据', () => {
    const settings = { language: 'zh', theme: 'dark' };
    const metadata = { categories: [{ id: 'food', name: '餐饮' }], rules: [] };
    const bundle = createBackupBundle(
      [{ path: 'other.bean', content: 'some content' }],
      '2026-07-01 * "x" "y"\n',
      settings,
      metadata,
    );
    expect(bundle.version).toBe(2);
    expect(bundle.files).toHaveLength(1);
    expect(bundle.mainBean).toContain('2026-07-01');
    expect(bundle.settings).toEqual(settings);
    expect(bundle.metadata).toEqual(metadata);

    const json = serializeBundle(bundle);
    const restored = deserializeBundle(json);
    expect(restored.mainBean).toBe(bundle.mainBean);
    expect(restored.settings).toEqual(settings);
    expect(restored.metadata).toEqual(metadata);
  });

  it('restoreFiles 含 main.bean', () => {
    const bundle: BackupBundle = { version: 1, createdAt: '2026-07-13', files: [{ path: 'other.bean', content: 'x' }], mainBean: 'y' };
    const files = restoreFiles(bundle);
    expect(files).toHaveLength(2);
    expect(files.find(f => f.path === 'main.bean')?.content).toBe('y');
  });

  it('deserializeBundle 兼容 v1（无 settings/metadata）', () => {
    const v1 = '{"version":1,"createdAt":"2026-01-01","files":[],"mobileBean":""}';
    const bundle = deserializeBundle(v1);
    expect(bundle.version).toBe(1);
    expect(bundle.settings).toBeUndefined();
    expect(bundle.metadata).toBeUndefined();
  });

  it('deserializeBundle 拒绝错误版本', () => {
    expect(() => deserializeBundle('{"version":99,"files":[],"mobileBean":""}')).toThrow('不支持的备份版本');
  });
});
