import { describe, expect, it, vi } from 'vitest';
import { ErrorCategory, isRecoverable, isRetryable, toAppError, withRetry } from '../src/utils/errorHandler';
import { LogLevel, createLogger } from '../src/utils/logger';

describe('errorHandler - toAppError', () => {
  it('把 Error 归一化为 AppError', () => {
    const appErr = toAppError(new Error('test'), ErrorCategory.NETWORK);
    expect(appErr.category).toBe(ErrorCategory.NETWORK);
    expect(appErr.message).toBe('test');
    expect(appErr.retryable).toBe(true);  // NETWORK 可重试
  });

  it('把字符串归一化', () => {
    const appErr = toAppError('字符串错误', ErrorCategory.PARSING);
    expect(appErr.message).toBe('字符串错误');
    expect(appErr.retryable).toBe(false);  // PARSING 不可重试
  });

  it('AppError 再经过 toAppError 保持不变', () => {
    const appErr = toAppError(new Error('x'), ErrorCategory.DATABASE);
    const again = toAppError(appErr);
    expect(again).toBe(appErr);
  });
});

describe('errorHandler - withRetry', () => {
  it('首次成功不重试', async () => {
    const fn = vi.fn().mockResolvedValue('ok');
    const result = await withRetry(fn, 3, 10);
    expect(result).toBe('ok');
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('失败后重试到成功', async () => {
    const fn = vi.fn()
      .mockRejectedValueOnce(new Error('fail1'))
      .mockRejectedValueOnce(new Error('fail2'))
      .mockResolvedValueOnce('ok');
    const result = await withRetry(fn, 3, 10);
    expect(result).toBe('ok');
    expect(fn).toHaveBeenCalledTimes(3);
  });

  it('重试次数耗尽后抛出最后一个错误', async () => {
    const fn = vi.fn().mockRejectedValue(new Error('always fail'));
    await expect(withRetry(fn, 2, 10)).rejects.toThrow('always fail');
    expect(fn).toHaveBeenCalledTimes(3);  // 首次 + 2 次重试
  });

  it('retries=0 时不重试', async () => {
    const fn = vi.fn().mockRejectedValue(new Error('x'));
    await expect(withRetry(fn, 0, 10)).rejects.toThrow('x');
    expect(fn).toHaveBeenCalledTimes(1);
  });
});

describe('errorHandler - isRetryable/isRecoverable', () => {
  it('NETWORK 错误可重试', () => {
    const err = toAppError(new Error('x'), ErrorCategory.NETWORK);
    expect(isRetryable(err)).toBe(true);
    expect(isRecoverable(err)).toBe(true);
  });

  it('PARSING 错误不可重试', () => {
    const err = toAppError(new Error('x'), ErrorCategory.PARSING);
    expect(isRetryable(err)).toBe(false);
    expect(isRecoverable(err)).toBe(false);
  });

  it('未知错误默认可恢复', () => {
    expect(isRecoverable(new Error('x'))).toBe(true);
    expect(isRetryable(new Error('x'))).toBe(false);
  });
});

describe('logger', () => {
  it('记录各级别日志', () => {
    const log = createLogger(100);
    log.consoleOutput = false;  // 测试不输出 console
    log.debug('tag1', 'debug msg');
    log.info('tag1', 'info msg');
    log.warn('tag2', 'warn msg');
    log.error('tag2', 'error msg', { code: 500 });
    const logs = log.getLogs();
    expect(logs).toHaveLength(4);
    expect(logs[0].level).toBe(LogLevel.DEBUG);
    expect(logs[3].level).toBe(LogLevel.ERROR);
    expect(logs[3].data).toEqual({ code: 500 });
  });

  it('循环缓冲：超出上限丢弃最旧', () => {
    const log = createLogger(3);
    log.consoleOutput = false;
    log.info('t', 'msg1');
    log.info('t', 'msg2');
    log.info('t', 'msg3');
    log.info('t', 'msg4');  // 触发裁剪，丢弃 msg1
    const logs = log.getLogs();
    expect(logs).toHaveLength(3);
    expect(logs[0].message).toBe('msg2');
    expect(logs[2].message).toBe('msg4');
  });

  it('minLevel 过滤低级别', () => {
    const log = createLogger(100);
    log.consoleOutput = false;
    log.minLevel = LogLevel.WARN;
    log.debug('t', 'debug');  // 被过滤
    log.info('t', 'info');    // 被过滤
    log.warn('t', 'warn');
    log.error('t', 'error');
    expect(log.getLogs()).toHaveLength(2);
  });

  it('query 按级别和标签过滤', () => {
    const log = createLogger(100);
    log.consoleOutput = false;
    log.info('alpha', 'a1');
    log.error('alpha', 'a2');
    log.error('beta', 'b1');
    expect(log.query({ tag: 'alpha' })).toHaveLength(2);
    expect(log.query({ minLevel: LogLevel.ERROR })).toHaveLength(2);
    expect(log.query({ minLevel: LogLevel.ERROR, tag: 'alpha' })).toHaveLength(1);
  });

  it('clearLogs 清空', () => {
    const log = createLogger(100);
    log.consoleOutput = false;
    log.info('t', 'x');
    log.clearLogs();
    expect(log.getLogs()).toHaveLength(0);
  });

  it('setMaxBuffer 动态调整并立即裁剪', () => {
    const log = createLogger(100);
    log.consoleOutput = false;
    log.info('t', 'm1');
    log.info('t', 'm2');
    log.info('t', 'm3');
    log.setMaxBuffer(2);
    expect(log.getLogs()).toHaveLength(2);
    expect(log.getLogs()[0].message).toBe('m2');
  });

  it('日志条目含 ISO 时间戳', () => {
    const log = createLogger(100);
    log.consoleOutput = false;
    log.info('t', 'x');
    const entry = log.getLogs()[0];
    expect(() => new Date(entry.timestamp).toISOString()).not.toThrow();
    expect(new Date(entry.timestamp).getTime()).toBeLessThanOrEqual(Date.now());
  });
});
