/**
 * 检查点系统测试
 */

import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import * as path from 'path';
import * as fs from 'fs/promises';
import * as os from 'os';
import {
  CheckpointManager,
  ShadowGit,
  hashWorkingDir,
} from '../../src/checkpoint/index.js';

describe('hashWorkingDir', () => {
  it('should generate consistent hash for same path', () => {
    const path1 = '/Users/test/project';
    const path2 = '/Users/test/project';

    expect(hashWorkingDir(path1)).toBe(hashWorkingDir(path2));
  });

  it('should generate different hash for different paths', () => {
    const path1 = '/Users/test/project1';
    const path2 = '/Users/test/project2';

    expect(hashWorkingDir(path1)).not.toBe(hashWorkingDir(path2));
  });

  it('should generate 13-character hash', () => {
    const hash = hashWorkingDir('/some/test/path');
    expect(hash.length).toBe(13);
  });

  it('should only contain digits', () => {
    const hash = hashWorkingDir('/some/test/path');
    expect(/^\d+$/.test(hash)).toBe(true);
  });
});

describe('ShadowGit', () => {
  let tempDir: string;
  let storageDir: string;
  let shadowGit: ShadowGit;

  beforeAll(async () => {
    tempDir = path.join(os.tmpdir(), `checkpoint-test-${Date.now()}`);
    storageDir = path.join(os.tmpdir(), `checkpoint-storage-${Date.now()}`);

    await fs.mkdir(tempDir, { recursive: true });
    await fs.mkdir(storageDir, { recursive: true });

    shadowGit = new ShadowGit(tempDir, storageDir);
  });

  afterAll(async () => {
    try {
      await fs.rm(tempDir, { recursive: true, force: true });
      await fs.rm(storageDir, { recursive: true, force: true });
    } catch {
      // ignore cleanup errors
    }
  });

  it('should initialize shadow git repository', async () => {
    await shadowGit.initialize();

    const gitDir = path.join(shadowGit.getShadowGitDir(), '.git');
    const stat = await fs.stat(gitDir);
    expect(stat.isDirectory()).toBe(true);
  });

  it('should create commit and return hash', async () => {
    // 创建测试文件
    await fs.writeFile(path.join(tempDir, 'test.txt'), 'Hello World');

    const commitHash = await shadowGit.createCommit('Test commit');

    expect(commitHash).toBeDefined();
    expect(commitHash.length).toBe(40); // Git SHA-1 hash length
  });

  it('should list commits', async () => {
    const commits = await shadowGit.getCommits(10);

    expect(commits.length).toBeGreaterThan(0);
    expect(commits[0].hash).toBeDefined();
    expect(commits[0].message).toBeDefined();
    expect(commits[0].timestamp).toBeGreaterThan(0);
  });

  it('should detect changes', async () => {
    // 修改文件
    await fs.writeFile(path.join(tempDir, 'test.txt'), 'Modified content');

    const hasChanges = await shadowGit.hasChanges();
    expect(hasChanges).toBe(true);
  });

  it('should get diff summary', async () => {
    // 先提交当前状态
    await shadowGit.createCommit('Before diff test');

    // 修改文件
    await fs.writeFile(path.join(tempDir, 'test.txt'), 'New content for diff');
    await shadowGit.createCommit('After diff test');

    const commits = await shadowGit.getCommits(2);
    const diff = await shadowGit.getDiffSummary(commits[1].hash, commits[0].hash);

    expect(diff.from).toBe(commits[1].hash);
    expect(diff.to).toBe(commits[0].hash);
    expect(diff.files.length).toBeGreaterThanOrEqual(0);
  });

  it('should reset to previous commit', async () => {
    const commits = await shadowGit.getCommits(2);
    if (commits.length < 2) return; // 跳过如果没有足够的 commits

    const olderCommit = commits[1].hash;

    await shadowGit.resetHard(olderCommit);

    const currentHead = await shadowGit.getHead();
    expect(currentHead).toBe(olderCommit);
  });
});

describe('CheckpointManager', () => {
  let tempDir: string;
  let storageDir: string;
  let manager: CheckpointManager;

  beforeEach(async () => {
    tempDir = path.join(os.tmpdir(), `checkpoint-mgr-test-${Date.now()}`);
    storageDir = path.join(os.tmpdir(), `checkpoint-mgr-storage-${Date.now()}`);

    await fs.mkdir(tempDir, { recursive: true });

    manager = new CheckpointManager(tempDir, {
      storageDir,
      maxCheckpoints: 10,
      maxAge: 24 * 60 * 60 * 1000,
    });
  });

  afterAll(async () => {
    try {
      await fs.rm(tempDir, { recursive: true, force: true });
      await fs.rm(storageDir, { recursive: true, force: true });
    } catch {
      // ignore cleanup errors
    }
  });

  it('should initialize manager', async () => {
    await manager.initialize();
    expect(manager.isEnabled()).toBe(true);
  });

  it('should create checkpoint', async () => {
    await fs.writeFile(path.join(tempDir, 'file1.txt'), 'Content 1');

    const checkpoint = await manager.createCheckpoint({
      name: 'Test checkpoint',
      description: 'A test checkpoint',
    });

    expect(checkpoint.id).toBeDefined();
    expect(checkpoint.name).toBe('Test checkpoint');
    expect(checkpoint.description).toBe('A test checkpoint');
    expect(checkpoint.commitHash).toBeDefined();
    expect(checkpoint.timestamp).toBeGreaterThan(0);
  });

  it('should list checkpoints', async () => {
    // 创建几个检查点
    await fs.writeFile(path.join(tempDir, 'file2.txt'), 'Content 2');
    await manager.createCheckpoint({ name: 'Checkpoint 1' });

    await fs.writeFile(path.join(tempDir, 'file3.txt'), 'Content 3');
    await manager.createCheckpoint({ name: 'Checkpoint 2' });

    const checkpoints = await manager.listCheckpoints();

    expect(checkpoints.length).toBeGreaterThanOrEqual(2);
    // 应该按时间倒序排列
    expect(checkpoints[0].timestamp).toBeGreaterThanOrEqual(checkpoints[1].timestamp);
  });

  it('should get checkpoint by id', async () => {
    await fs.writeFile(path.join(tempDir, 'file4.txt'), 'Content 4');
    const created = await manager.createCheckpoint({ name: 'Find me' });

    const found = await manager.getCheckpoint(created.id);

    expect(found).not.toBeNull();
    expect(found!.id).toBe(created.id);
    expect(found!.name).toBe('Find me');
  });

  it('should get latest checkpoint', async () => {
    await fs.writeFile(path.join(tempDir, 'file5.txt'), 'Content 5');
    const cp1 = await manager.createCheckpoint({ name: 'Older' });

    // 等待一小段时间确保时间戳不同
    await new Promise((resolve) => setTimeout(resolve, 100));

    await fs.writeFile(path.join(tempDir, 'file6.txt'), 'Content 6');
    const cp2 = await manager.createCheckpoint({ name: 'Newer' });

    const latest = await manager.getLatestCheckpoint();

    expect(latest).not.toBeNull();
    expect(latest!.id).toBe(cp2.id);
  });

  it('should determine if checkpoint should be created for tool', () => {
    expect(manager.shouldCreateCheckpoint('write_file')).toBe(true);
    expect(manager.shouldCreateCheckpoint('edit_file')).toBe(true);
    expect(manager.shouldCreateCheckpoint('delete_file')).toBe(true);
    expect(manager.shouldCreateCheckpoint('bash')).toBe(false); // 默认禁用
    expect(manager.shouldCreateCheckpoint('read_file')).toBe(false);
  });

  it('should create checkpoint before tool execution', async () => {
    await fs.writeFile(path.join(tempDir, 'before-tool.txt'), 'Before');

    const checkpointId = await manager.beforeToolExecution('write_file', {
      path: path.join(tempDir, 'new-file.txt'),
    });

    expect(checkpointId).not.toBeNull();

    const checkpoint = await manager.getCheckpoint(checkpointId!);
    expect(checkpoint).not.toBeNull();
    expect(checkpoint!.trigger).toBe('tool:write_file');
  });

  it('should get diff between checkpoint and current state', async () => {
    await fs.writeFile(path.join(tempDir, 'diff-test.txt'), 'Original');
    const checkpoint = await manager.createCheckpoint({ name: 'Before change' });

    // 修改文件
    await fs.writeFile(path.join(tempDir, 'diff-test.txt'), 'Modified');

    const diff = await manager.getDiff(checkpoint.id);

    expect(diff).toBeDefined();
    expect(diff.from).toBe(checkpoint.commitHash);
  });

  it('should rollback to checkpoint (dry run)', async () => {
    await fs.writeFile(path.join(tempDir, 'rollback-test.txt'), 'Original');
    const checkpoint = await manager.createCheckpoint({ name: 'Before rollback' });

    // 修改文件
    await fs.writeFile(path.join(tempDir, 'rollback-test.txt'), 'Modified');
    await manager.createCheckpoint({ name: 'After change' });

    // 预览回滚
    const result = await manager.rollback({
      target: checkpoint.id,
      dryRun: true,
    });

    expect(result.success).toBe(true);
    expect(result.restoredFiles.length).toBeGreaterThanOrEqual(0);

    // 文件应该保持修改状态（因为是 dry run）
    const content = await fs.readFile(path.join(tempDir, 'rollback-test.txt'), 'utf-8');
    expect(content).toBe('Modified');
  });

  it('should emit events on checkpoint creation', async () => {
    const events: any[] = [];
    manager.addEventListener((event) => {
      events.push(event);
    });

    await fs.writeFile(path.join(tempDir, 'event-test.txt'), 'Event test');
    await manager.createCheckpoint({ name: 'Event test' });

    expect(events.length).toBeGreaterThan(0);
    expect(events.some((e) => e.type === 'created')).toBe(true);
  });

  it('should get stats', async () => {
    const stats = await manager.getStats();

    expect(stats.count).toBeGreaterThanOrEqual(0);
    // oldest 和 newest 可能为 null 如果没有检查点
    if (stats.count > 0) {
      expect(stats.oldestTimestamp).not.toBeNull();
      expect(stats.newestTimestamp).not.toBeNull();
    }
  });
});
