/**
 * Git 深度集成测试
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as path from 'path';
import * as fs from 'fs/promises';
import * as os from 'os';
import { execSync } from 'child_process';
import {
  GitRepo,
  GitManager,
  MessageGenerator,
  UndoManager,
  initGitManager,
  getGitManager,
  resetGitManager,
  DEFAULT_GIT_CONFIG,
  type DiffResult,
} from '../../src/git/index.js';

describe('MessageGenerator', () => {
  const generator = new MessageGenerator({
    style: 'conventional',
    includeFileList: true,
    maxLength: 72,
    useAI: false,
  });

  it('should generate conventional commit message', () => {
    const diff: DiffResult = {
      files: [
        { path: 'src/test.ts', status: 'modified', hunks: [], binary: false },
      ],
      stats: { filesChanged: 1, insertions: 10, deletions: 5 },
    };

    const message = generator.generate(diff, ['src/test.ts']);
    expect(message).toMatch(/^(feat|fix|chore|test|docs|style|refactor)/);
    expect(message).toContain('test');
  });

  it('should detect test type for test files', () => {
    const diff: DiffResult = {
      files: [
        { path: 'tests/foo.test.ts', status: 'added', hunks: [], binary: false },
      ],
      stats: { filesChanged: 1, insertions: 50, deletions: 0 },
    };

    const message = generator.generate(diff, ['tests/foo.test.ts']);
    expect(message).toMatch(/^test/);
  });

  it('should detect docs type for markdown files', () => {
    const diff: DiffResult = {
      files: [
        { path: 'README.md', status: 'modified', hunks: [], binary: false },
      ],
      stats: { filesChanged: 1, insertions: 20, deletions: 10 },
    };

    const message = generator.generate(diff, ['README.md']);
    expect(message).toMatch(/^docs/);
  });

  it('should include scope when files are in same directory', () => {
    const diff: DiffResult = {
      files: [
        { path: 'src/git/repo.ts', status: 'modified', hunks: [], binary: false },
        { path: 'src/git/types.ts', status: 'modified', hunks: [], binary: false },
      ],
      stats: { filesChanged: 2, insertions: 30, deletions: 20 },
    };

    const message = generator.generate(diff, ['src/git/repo.ts', 'src/git/types.ts']);
    expect(message).toContain('(git)');
  });

  it('should generate simple format message', () => {
    const simpleGenerator = new MessageGenerator({
      style: 'simple',
      includeFileList: false,
      maxLength: 72,
      useAI: false,
    });

    const diff: DiffResult = {
      files: [
        { path: 'src/index.ts', status: 'modified', hunks: [], binary: false },
      ],
      stats: { filesChanged: 1, insertions: 5, deletions: 2 },
    };

    const message = simpleGenerator.generate(diff, ['src/index.ts']);
    expect(message).toContain('index');
    expect(message.length).toBeLessThanOrEqual(72);
  });
});

describe('GitRepo', () => {
  let tempDir: string;
  let repo: GitRepo;

  beforeEach(async () => {
    // 创建临时目录
    tempDir = path.join(os.tmpdir(), `git-test-${Date.now()}`);
    await fs.mkdir(tempDir, { recursive: true });

    // 初始化 Git 仓库
    execSync('git init', { cwd: tempDir, stdio: 'ignore' });
    execSync('git config user.email "test@test.com"', { cwd: tempDir, stdio: 'ignore' });
    execSync('git config user.name "Test User"', { cwd: tempDir, stdio: 'ignore' });

    // 创建初始提交
    await fs.writeFile(path.join(tempDir, 'README.md'), '# Test');
    execSync('git add .', { cwd: tempDir, stdio: 'ignore' });
    execSync('git commit -m "Initial commit"', { cwd: tempDir, stdio: 'ignore' });

    repo = new GitRepo(tempDir, DEFAULT_GIT_CONFIG);
    await repo.initialize();
  });

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

  it('should initialize and detect git repo', async () => {
    const isRepo = await repo.initialize();
    expect(isRepo).toBe(true);
  });

  it('should get status', async () => {
    const status = await repo.getStatus();
    expect(status.branch).toMatch(/^(main|master)$/);
    expect(status.isDirty).toBe(false);
  });

  it('should detect dirty files', async () => {
    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');

    const isDirty = await repo.isDirty();
    expect(isDirty).toBe(true);
  });

  it('should get diff', async () => {
    await fs.writeFile(path.join(tempDir, 'README.md'), '# Test\n\nUpdated');

    const diff = await repo.getDiff();
    expect(diff.files.length).toBe(1);
    expect(diff.files[0].path).toBe('README.md');
  });

  it('should commit changes', async () => {
    await fs.writeFile(path.join(tempDir, 'new.txt'), 'new content');

    const result = await repo.commit({
      files: ['new.txt'],
      message: 'Add new file',
      aiEdits: true,
    });

    expect(result.success).toBe(true);
    expect(result.shortHash).toBeDefined();
    expect(repo.isAICommit(result.shortHash!)).toBe(true);
  });

  it('should get recent commits', async () => {
    const commits = await repo.getRecentCommits(5);
    expect(commits.length).toBeGreaterThanOrEqual(1);
    expect(commits[0].message).toBe('Initial commit');
  });

  it('should get HEAD commit', async () => {
    const head = await repo.getHeadCommit();
    expect(head).toBeDefined();
    expect(head!.length).toBe(40);
  });

  it('should get current branch', async () => {
    const branch = await repo.getCurrentBranch();
    expect(['main', 'master']).toContain(branch);
  });
});

describe('UndoManager', () => {
  let tempDir: string;
  let repo: GitRepo;
  let undoManager: UndoManager;

  beforeEach(async () => {
    tempDir = path.join(os.tmpdir(), `undo-test-${Date.now()}`);
    await fs.mkdir(tempDir, { recursive: true });

    execSync('git init', { cwd: tempDir, stdio: 'ignore' });
    execSync('git config user.email "test@test.com"', { cwd: tempDir, stdio: 'ignore' });
    execSync('git config user.name "Test User"', { cwd: tempDir, stdio: 'ignore' });

    await fs.writeFile(path.join(tempDir, 'README.md'), '# Test');
    execSync('git add .', { cwd: tempDir, stdio: 'ignore' });
    execSync('git commit -m "Initial commit"', { cwd: tempDir, stdio: 'ignore' });

    repo = new GitRepo(tempDir, DEFAULT_GIT_CONFIG);
    await repo.initialize();

    undoManager = new UndoManager(repo, { maxHistory: 50 });
  });

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

  it('should return error when nothing to undo', async () => {
    const result = await undoManager.undo();
    expect(result.success).toBe(false);
    expect(result.message).toContain('Nothing to undo');
  });

  it('should record and undo AI commit', async () => {
    // 创建 AI 提交
    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');
    const commitResult = await repo.commit({
      files: ['new.txt'],
      message: 'Add new file',
      aiEdits: true,
    });

    expect(commitResult.success).toBe(true);

    // 记录到 undo 历史
    undoManager.recordCommit(
      commitResult.hash!,
      commitResult.shortHash!,
      commitResult.message!,
      ['new.txt']
    );

    // 执行 undo
    const undoResult = await undoManager.undo();
    expect(undoResult.success).toBe(true);
    expect(undoResult.commitHash).toBe(commitResult.shortHash);
  });

  it('should reject undo when repository changed', async () => {
    // 创建 AI 提交
    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');
    const commitResult = await repo.commit({
      files: ['new.txt'],
      message: 'Add new file',
      aiEdits: true,
    });

    undoManager.recordCommit(
      commitResult.hash!,
      commitResult.shortHash!,
      commitResult.message!,
      ['new.txt']
    );

    // 在 AI 提交后又创建了一个手动提交
    await fs.writeFile(path.join(tempDir, 'manual.txt'), 'manual');
    execSync('git add .', { cwd: tempDir, stdio: 'ignore' });
    execSync('git commit -m "Manual commit"', { cwd: tempDir, stdio: 'ignore' });

    // 尝试 undo
    const undoResult = await undoManager.undo();
    expect(undoResult.success).toBe(false);
    expect(undoResult.message).toContain('does not match');
  });

  it('should get undo preview', () => {
    // 没有记录时返回 null
    expect(undoManager.getUndoPreview()).toBeNull();

    // 记录后可以获取预览
    undoManager.recordCommit('abc1234', 'abc1234', 'Test commit', ['test.txt']);
    const preview = undoManager.getUndoPreview();
    expect(preview).not.toBeNull();
    expect(preview!.message).toBe('Test commit');
  });

  it('should check if can undo', async () => {
    let canUndo = await undoManager.canUndo();
    expect(canUndo.canUndo).toBe(false);

    // 创建并记录 AI 提交
    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');
    const commitResult = await repo.commit({
      files: ['new.txt'],
      message: 'Add new file',
      aiEdits: true,
    });

    undoManager.recordCommit(
      commitResult.hash!,
      commitResult.shortHash!,
      commitResult.message!,
      ['new.txt']
    );

    canUndo = await undoManager.canUndo();
    expect(canUndo.canUndo).toBe(true);
  });
});

describe('GitManager', () => {
  let tempDir: string;

  beforeEach(async () => {
    resetGitManager();

    tempDir = path.join(os.tmpdir(), `manager-test-${Date.now()}`);
    await fs.mkdir(tempDir, { recursive: true });

    execSync('git init', { cwd: tempDir, stdio: 'ignore' });
    execSync('git config user.email "test@test.com"', { cwd: tempDir, stdio: 'ignore' });
    execSync('git config user.name "Test User"', { cwd: tempDir, stdio: 'ignore' });

    await fs.writeFile(path.join(tempDir, 'README.md'), '# Test');
    execSync('git add .', { cwd: tempDir, stdio: 'ignore' });
    execSync('git commit -m "Initial commit"', { cwd: tempDir, stdio: 'ignore' });
  });

  afterEach(async () => {
    resetGitManager();
    try {
      await fs.rm(tempDir, { recursive: true, force: true });
    } catch {
      // ignore
    }
  });

  it('should initialize git manager', async () => {
    const manager = await initGitManager(tempDir);
    expect(manager).not.toBeNull();
    expect(manager!.isInitialized()).toBe(true);
  });

  it('should return null for non-git directory', async () => {
    const nonGitDir = path.join(os.tmpdir(), `non-git-${Date.now()}`);
    await fs.mkdir(nonGitDir, { recursive: true });

    const manager = await initGitManager(nonGitDir);
    expect(manager).toBeNull();

    await fs.rm(nonGitDir, { recursive: true, force: true });
  });

  it('should get global manager', async () => {
    expect(getGitManager()).toBeNull();

    await initGitManager(tempDir);
    expect(getGitManager()).not.toBeNull();
  });

  it('should get status', async () => {
    const manager = await initGitManager(tempDir);
    const status = await manager!.getStatus();

    expect(status.branch).toMatch(/^(main|master)$/);
    expect(status.isDirty).toBe(false);
  });

  it('should commit manually', async () => {
    const manager = await initGitManager(tempDir);

    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');

    const result = await manager!.commit({
      files: ['new.txt'],
      message: 'Add new file',
    });

    expect(result.success).toBe(true);
    expect(result.shortHash).toBeDefined();
  });

  it('should undo AI commit', async () => {
    const manager = await initGitManager(tempDir);

    // 创建并提交文件
    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');
    const commitResult = await manager!.commit({
      files: ['new.txt'],
      message: 'Add new file',
    });

    expect(commitResult.success).toBe(true);

    // 执行 undo
    const undoResult = await manager!.undo();
    expect(undoResult.success).toBe(true);
  });

  it('should get undo history', async () => {
    const manager = await initGitManager(tempDir);

    expect(manager!.getUndoHistory()).toHaveLength(0);

    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');
    await manager!.commit({
      files: ['new.txt'],
      message: 'Add new file',
    });

    expect(manager!.getUndoHistory()).toHaveLength(1);
  });

  it('should emit events', async () => {
    const manager = await initGitManager(tempDir);
    const events: any[] = [];

    manager!.addEventListener((event) => events.push(event));

    await fs.writeFile(path.join(tempDir, 'new.txt'), 'content');
    await manager!.commit({
      files: ['new.txt'],
      message: 'Add new file',
    });

    expect(events.length).toBe(1);
    expect(events[0].type).toBe('commit');
  });
});
