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

// 定义可控的 mock 变量
let mockExecAsyncResult: { stdout: string; stderr: string } | Error = {
  stdout: '[main abc1234] Test commit\n 1 file changed, 1 insertion(+)',
  stderr: '',
};

let mockPermissionResult = {
  allowed: true,
  action: 'allow' as const,
  reason: undefined as string | undefined,
  needsConfirmation: false,
};

// Mock child_process
vi.mock('child_process', () => ({
  exec: vi.fn(),
}));

// Mock util - 返回函数使用外部变量
vi.mock('util', () => ({
  promisify: vi.fn(() => vi.fn(async () => {
    if (mockExecAsyncResult instanceof Error) {
      throw mockExecAsyncResult;
    }
    return mockExecAsyncResult;
  })),
}));

// Mock permission manager
vi.mock('../../../../src/permission/index.js', () => ({
  getPermissionManager: vi.fn(() => ({
    checkGitPermission: vi.fn(async () => mockPermissionResult),
  })),
}));

// Mock loadDescription
vi.mock('../../../../src/tools/load_description.js', () => ({
  loadDescription: vi.fn(() => '提交 Git 变更'),
}));

import { gitCommitTool } from '../../../../src/tools/git/git_commit.js';
import { getPermissionManager } from '../../../../src/permission/index.js';

describe('gitCommitTool - Git 提交工具扩展测试', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockExecAsyncResult = {
      stdout: '[main abc1234] Test commit\n 1 file changed, 1 insertion(+)',
      stderr: '',
    };
    mockPermissionResult = {
      allowed: true,
      action: 'allow',
      reason: undefined,
      needsConfirmation: false,
    };
  });

  describe('基本提交', () => {
    it('成功提交变更', async () => {
      const result = await gitCommitTool.execute({
        message: 'Test commit message',
      });

      expect(result.success).toBe(true);
      expect(result.output).toContain('Test commit');
    });

    it('没有 message 返回错误', async () => {
      const result = await gitCommitTool.execute({});

      expect(result.success).toBe(false);
      expect(result.error).toContain('提交信息是必填的');
    });

    it('amend 模式无 message 允许', async () => {
      const result = await gitCommitTool.execute({
        amend: true,
      });

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

  describe('提交选项', () => {
    it('使用 -a 选项暂存所有变更', async () => {
      const result = await gitCommitTool.execute({
        message: 'Auto stage commit',
        all: true,
      });

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

    it('amend 带 message', async () => {
      const result = await gitCommitTool.execute({
        message: 'Updated message',
        amend: true,
      });

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

    it('转义 message 中的引号', async () => {
      const result = await gitCommitTool.execute({
        message: 'Message with "quotes"',
      });

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

  describe('权限检查', () => {
    it('权限拒绝返回错误', async () => {
      mockPermissionResult = {
        allowed: false,
        action: 'deny',
        reason: '不允许提交到此仓库',
        needsConfirmation: false,
      };

      const result = await gitCommitTool.execute({
        message: 'Test commit',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('权限被拒绝');
      expect(result.error).toContain('不允许提交到此仓库');
    });

    it('需要确认时返回提示', async () => {
      mockPermissionResult = {
        allowed: false,
        action: 'ask',
        reason: '首次提交',
        needsConfirmation: true,
      };

      const result = await gitCommitTool.execute({
        message: 'First commit',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('需要用户确认');
      expect(result.error).toContain('首次提交');
    });

    it('权限检查包含正确上下文', async () => {
      const mockCheck = vi.fn().mockResolvedValue({
        allowed: true,
        action: 'allow',
      });
      vi.mocked(getPermissionManager).mockReturnValue({
        checkGitPermission: mockCheck,
      } as any);

      await gitCommitTool.execute({
        message: 'Check context',
      });

      expect(mockCheck).toHaveBeenCalledWith({
        operation: 'commit',
        message: 'Check context',
      });
    });
  });

  describe('错误处理', () => {
    it('没有变更可提交', async () => {
      mockExecAsyncResult = Object.assign(
        new Error('nothing to commit, working tree clean'),
        { stdout: '', stderr: '', message: 'nothing to commit, working tree clean' }
      );

      const result = await gitCommitTool.execute({
        message: 'Empty commit',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('没有变更需要提交');
      expect(result.error).toContain('git_add');
    });

    it('stderr 包含 nothing to commit', async () => {
      mockExecAsyncResult = Object.assign(
        new Error('Command failed'),
        { stdout: '', stderr: 'nothing to commit', message: 'Command failed' }
      );

      const result = await gitCommitTool.execute({
        message: 'Empty commit',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('没有变更需要提交');
    });

    it('其他 Git 错误', async () => {
      mockExecAsyncResult = Object.assign(
        new Error('fatal: not a git repository'),
        { stdout: '', stderr: 'fatal: not a git repository', message: 'fatal: not a git repository' }
      );

      const result = await gitCommitTool.execute({
        message: 'Test commit',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('not a git repository');
    });

    it('保留 stdout 在错误中', async () => {
      mockExecAsyncResult = Object.assign(
        new Error('error'),
        { stdout: 'some output', stderr: 'error message', message: 'error' }
      );

      const result = await gitCommitTool.execute({
        message: 'Test',
      });

      expect(result.output).toBe('some output');
      expect(result.error).toBe('error message');
    });
  });

  describe('工具元数据', () => {
    it('包含正确的名称', () => {
      expect(gitCommitTool.name).toBe('git_commit');
    });

    it('包含正确的类别', () => {
      expect(gitCommitTool.metadata.category).toBe('git');
    });

    it('包含关键词', () => {
      expect(gitCommitTool.metadata.keywords).toContain('git');
      expect(gitCommitTool.metadata.keywords).toContain('commit');
      expect(gitCommitTool.metadata.keywords).toContain('提交');
    });

    it('参数定义正确', () => {
      expect(gitCommitTool.parameters.message.required).toBe(true);
      expect(gitCommitTool.parameters.amend.required).toBe(false);
      expect(gitCommitTool.parameters.all.required).toBe(false);
    });
  });

  describe('输出格式', () => {
    it('包含 stdout', async () => {
      mockExecAsyncResult = {
        stdout: 'commit output',
        stderr: '',
      };

      const result = await gitCommitTool.execute({ message: 'test' });

      expect(result.output).toBe('commit output');
    });

    it('包含 stdout 和 stderr', async () => {
      mockExecAsyncResult = {
        stdout: 'commit output',
        stderr: 'warning message',
      };

      const result = await gitCommitTool.execute({ message: 'test' });

      expect(result.output).toContain('commit output');
      expect(result.output).toContain('warning message');
    });
  });
});
