import { describe, expect, it, vi, beforeEach } from 'vitest';

/**
 * FileSystemBackend 逻辑测试。
 *
 * 由于 expo-file-system 在测试环境不可用，我们 mock 它的接口，
 * 验证 FileSystemBackend 的 read/writeAtomic/recoverIfNeeded 编排逻辑正确。
 */

// Mock expo-file-system/legacy（fileSystemBackend.ts 用 import * as，所以直接导出属性）
const mockFs = {
  documentDirectory: '/mock/',
  getInfoAsync: vi.fn(),
  readAsStringAsync: vi.fn(),
  writeAsStringAsync: vi.fn(),
  deleteAsync: vi.fn(),
  moveAsync: vi.fn(),
};

vi.mock('expo-file-system/legacy', () => mockFs);

// Mock logger
vi.mock('../src/utils/logger', () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

// 延迟导入（vi.mock 需要在模块加载前执行）
const { FileSystemBackend } = await import('../src/services/data/fileSystemBackend');

function resetMock() {
  mockFs.getInfoAsync.mockReset();
  mockFs.readAsStringAsync.mockReset();
  mockFs.writeAsStringAsync.mockReset();
  mockFs.deleteAsync.mockReset();
  mockFs.moveAsync.mockReset();
}

describe('FileSystemBackend', () => {
  beforeEach(resetMock);

  it('read() 返回文件内容', async () => {
    mockFs.getInfoAsync.mockResolvedValue({ exists: true });
    mockFs.readAsStringAsync.mockResolvedValue('2026-01-01 *\n  Assets:Bank');

    const backend = new FileSystemBackend();
    const content = await backend.read();
    expect(content).toBe('2026-01-01 *\n  Assets:Bank');
  });

  it('read() 文件不存在时返回空串', async () => {
    mockFs.getInfoAsync.mockResolvedValue({ exists: false });

    const backend = new FileSystemBackend();
    const content = await backend.read();
    expect(content).toBe('');
  });

  it('writeAtomic() 先写 tmp 再删旧文件再 rename', async () => {
    mockFs.getInfoAsync.mockResolvedValue({ exists: true });
    mockFs.writeAsStringAsync.mockResolvedValue(undefined);
    mockFs.deleteAsync.mockResolvedValue(undefined);
    mockFs.moveAsync.mockResolvedValue(undefined);

    const backend = new FileSystemBackend();
    await backend.writeAtomic('new content');

    // 验证写入 tmp
    expect(mockFs.writeAsStringAsync).toHaveBeenCalledWith('/mock/main.bean.tmp', 'new content');
    // 验证 rename tmp → 正式文件（覆盖旧文件，无需删除）
    expect(mockFs.moveAsync).toHaveBeenCalledWith({ from: '/mock/main.bean.tmp', to: '/mock/main.bean' });
  });

  it('writeAtomic() 旧文件不存在时不删除', async () => {
    mockFs.getInfoAsync.mockResolvedValue({ exists: false });
    mockFs.writeAsStringAsync.mockResolvedValue(undefined);
    mockFs.moveAsync.mockResolvedValue(undefined);

    const backend = new FileSystemBackend();
    await backend.writeAtomic('content');

    expect(mockFs.deleteAsync).not.toHaveBeenCalled();
    expect(mockFs.moveAsync).toHaveBeenCalled();
  });

  it('recoverIfNeeded() 检测到 tmp 残留时清理并返回 true', async () => {
    // 第一次 getInfoAsync（检测 tmp）返回存在
    mockFs.getInfoAsync.mockResolvedValue({ exists: true });
    mockFs.deleteAsync.mockResolvedValue(undefined);

    const backend = new FileSystemBackend();
    const result = await backend.recoverIfNeeded();
    expect(result).toBe(true);
    expect(mockFs.deleteAsync).toHaveBeenCalledWith('/mock/main.bean.tmp', { idempotent: true });
  });

  it('recoverIfNeeded() 无 tmp 残留时返回 false', async () => {
    mockFs.getInfoAsync.mockResolvedValue({ exists: false });

    const backend = new FileSystemBackend();
    const result = await backend.recoverIfNeeded();
    expect(result).toBe(false);
  });
});
