import { describe, expect, it, beforeEach } from 'vitest';
import { CrashReporter, MockSentryBridge, buildErrorRecoveryData, crashReporter } from '../src/services/crash';

describe('CrashReporter', () => {
  let reporter: CrashReporter;

  beforeEach(() => {
    reporter = new CrashReporter();
  });

  it('recordCrash 记录崩溃', () => {
    reporter.recordCrash({
      timestamp: new Date().toISOString(),
      message: '测试崩溃',
      source: 'uncaught',
    });
    expect(reporter.getRecords()).toHaveLength(1);
    expect(reporter.getLatestCrash()?.message).toBe('测试崩溃');
  });

  it('captureException 记录已处理异常', () => {
    reporter.captureException(new Error('已处理错误'));
    const records = reporter.getRecords();
    expect(records).toHaveLength(1);
    expect(records[0].source).toBe('handled');
    expect(records[0].stack).toContain('已处理错误');
  });

  it('getLatestCrash 返回最近一条', () => {
    reporter.recordCrash({ timestamp: '2026-07-13T10:00:00', message: 'first', source: 'uncaught' });
    reporter.recordCrash({ timestamp: '2026-07-13T11:00:00', message: 'second', source: 'uncaught' });
    expect(reporter.getLatestCrash()?.message).toBe('second');
  });

  it('超过上限丢弃最旧', () => {
    const small = new CrashReporter();
    (small as unknown as { maxRecords: number }).maxRecords = 3;
    for (let i = 0; i < 5; i++) {
      small.recordCrash({ timestamp: `2026-07-13T1${i}:00:00`, message: `crash${i}`, source: 'uncaught' });
    }
    expect(small.getRecords()).toHaveLength(3);
    expect(small.getRecords()[0].message).toBe('crash2');  // 最旧的2条被丢弃
  });

  it('exportAsText 导出文本', () => {
    reporter.recordCrash({ timestamp: '2026-07-13T10:00:00', message: 'msg1', source: 'uncaught' });
    const text = reporter.exportAsText();
    expect(text).toContain('msg1');
    expect(text).toContain('uncaught');
  });

  it('clear 清空', () => {
    reporter.recordCrash({ timestamp: 'now', message: 'x', source: 'uncaught' });
    reporter.clear();
    expect(reporter.getRecords()).toHaveLength(0);
  });

  it('setSentry 注入后 captureException 转发', () => {
    const sentry = new MockSentryBridge();
    reporter.setSentry(sentry, 'fake-dsn', 'test');
    expect(sentry.initialized).toBe(true);
    reporter.captureException(new Error('sentry error'));
    expect(sentry.exceptions).toHaveLength(1);
  });

  it('captureMessage 转发到 sentry', () => {
    const sentry = new MockSentryBridge();
    reporter.setSentry(sentry, 'dsn', 'test');
    reporter.captureMessage('info msg');
    reporter.captureMessage('warn msg', 'warning');
    expect(sentry.messages).toHaveLength(2);
  });

  it('install 不重复安装', () => {
    reporter.install();
    reporter.install();  // 第二次无副作用
    // 无直接断言，验证不抛错
  });
});

describe('buildErrorRecoveryData', () => {
  it('构造错误恢复 UI 数据', () => {
    const data = buildErrorRecoveryData(new Error('UI 崩溃'));
    expect(data.title).toBe('应用遇到问题');
    expect(data.message).toBe('UI 崩溃');
    expect(data.actions.length).toBeGreaterThan(0);
    expect(data.actions.some(a => a.type === 'retry')).toBe(true);
    expect(data.actions.some(a => a.type === 'export-logs')).toBe(true);
  });

  it('非 Error 对象也能构造', () => {
    const data = buildErrorRecoveryData('字符串错误');
    expect(data.message).toBe('字符串错误');
    expect(data.stack).toBeUndefined();
  });
});

describe('crashReporter 单例', () => {
  it('是 CrashReporter 实例', () => {
    expect(crashReporter).toBeInstanceOf(CrashReporter);
  });
});
