import { describe, it, expect, beforeEach, vi } from 'vitest';
import { writeFileTool } from '../../../../src/tools/filesystem/write_file.js';

// Mock fs/promises
vi.mock('fs/promises', () => ({
  mkdir: vi.fn().mockResolvedValue(undefined),
  writeFile: vi.fn().mockResolvedValue(undefined),
  readFile: vi.fn().mockRejectedValue(new Error('ENOENT: file not found')),
}));

// Mock permission manager
const mockCheckFilePermission = vi.fn();
vi.mock('../../../../src/permission/index.js', () => ({
  getPermissionManager: vi.fn(() => ({
    checkFilePermission: mockCheckFilePermission,
  })),
}));

// Mock loadDescription
vi.mock('../../../../src/tools/load_description.js', () => ({
  loadDescription: vi.fn(() => '写入文件内容'),
}));

// Mock LSP
const mockTouchFile = vi.fn();
const mockGetFormattedFileDiagnostics = vi.fn();
const mockIsLanguageSupported = vi.fn();

vi.mock('../../../../src/lsp/index.js', () => ({
  touchFile: (...args: unknown[]) => mockTouchFile(...args),
  getFormattedFileDiagnostics: (...args: unknown[]) => mockGetFormattedFileDiagnostics(...args),
  isLanguageSupported: (...args: unknown[]) => mockIsLanguageSupported(...args),
}));

import * as fs from 'fs/promises';

describe('writeFileTool - 写入文件工具扩展测试', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // 重置所有 mock 返回值
    vi.mocked(fs.mkdir).mockResolvedValue(undefined);
    vi.mocked(fs.writeFile).mockResolvedValue(undefined);
    mockCheckFilePermission.mockResolvedValue({
      allowed: true,
      action: 'allow',
    });
    mockIsLanguageSupported.mockReturnValue(false);
  });

  describe('LSP 集成', () => {
    it('支持的语言触发 LSP 诊断', async () => {
      mockIsLanguageSupported.mockReturnValue(true);
      mockTouchFile.mockResolvedValue(false);
      mockGetFormattedFileDiagnostics.mockResolvedValue(null);

      const result = await writeFileTool.execute({
        path: './test.ts',
        content: 'const x = 1;',
      });

      expect(result.success).toBe(true);
      expect(mockTouchFile).toHaveBeenCalled();
      expect(mockGetFormattedFileDiagnostics).toHaveBeenCalled();
    });

    it('首次启动等待更长时间', async () => {
      mockIsLanguageSupported.mockReturnValue(true);
      mockTouchFile.mockResolvedValue(true); // 首次启动返回 true
      mockGetFormattedFileDiagnostics.mockResolvedValue(null);

      const startTime = Date.now();
      await writeFileTool.execute({
        path: './test.ts',
        content: 'const x = 1;',
      });
      const elapsed = Date.now() - startTime;

      // 首次启动应该等待更长时间（2000ms vs 300ms）
      expect(elapsed).toBeGreaterThanOrEqual(1900);
    });

    it('有诊断问题时在输出中显示', async () => {
      mockIsLanguageSupported.mockReturnValue(true);
      mockTouchFile.mockResolvedValue(false);
      mockGetFormattedFileDiagnostics.mockResolvedValue(
        '\n<file_diagnostics>\n1:1 ERROR: Type error\n</file_diagnostics>'
      );

      const result = await writeFileTool.execute({
        path: './test.ts',
        content: 'const x: string = 1;',
      });

      expect(result.success).toBe(true);
      expect(result.output).toContain('代码检查发现问题');
      expect(result.output).toContain('file_diagnostics');
    });

    it('LSP 错误不影响主流程', async () => {
      mockIsLanguageSupported.mockReturnValue(true);
      mockTouchFile.mockRejectedValue(new Error('LSP error'));

      const result = await writeFileTool.execute({
        path: './test.ts',
        content: 'const x = 1;',
      });

      // 即使 LSP 失败，文件写入仍然成功
      expect(result.success).toBe(true);
      expect(result.output).toContain('文件已写入');
    });

    it('不支持的语言不触发 LSP', async () => {
      mockIsLanguageSupported.mockReturnValue(false);

      await writeFileTool.execute({
        path: './test.txt',
        content: 'plain text',
      });

      expect(mockTouchFile).not.toHaveBeenCalled();
      expect(mockGetFormattedFileDiagnostics).not.toHaveBeenCalled();
    });
  });

  describe('路径处理', () => {
    it('相对路径转换为绝对路径', async () => {
      await writeFileTool.execute({
        path: './relative/path/file.txt',
        content: 'content',
      });

      expect(fs.mkdir).toHaveBeenCalledWith(
        expect.stringMatching(/relative\/path$/),
        { recursive: true }
      );
    });

    it('绝对路径保持不变', async () => {
      await writeFileTool.execute({
        path: '/absolute/path/file.txt',
        content: 'content',
      });

      expect(fs.writeFile).toHaveBeenCalledWith(
        '/absolute/path/file.txt',
        'content',
        'utf-8'
      );
    });
  });

  describe('权限检查详情', () => {
    it('权限检查包含正确的上下文', async () => {
      await writeFileTool.execute({
        path: './test.txt',
        content: 'file content',
      });

      expect(mockCheckFilePermission).toHaveBeenCalledWith(
        expect.objectContaining({
          operation: 'write',
          newContent: 'file content',
        })
      );
    });

    it('被拒绝时返回具体原因', async () => {
      mockCheckFilePermission.mockResolvedValue({
        allowed: false,
        action: 'deny',
        reason: '系统文件不允许修改',
      });

      const result = await writeFileTool.execute({
        path: '/etc/passwd',
        content: 'malicious',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('权限被拒绝');
      expect(result.error).toContain('系统文件不允许修改');
    });

    it('需要确认时返回具体原因', async () => {
      mockCheckFilePermission.mockResolvedValue({
        allowed: false,
        action: 'ask',
        needsConfirmation: true,
        reason: '首次写入此文件',
      });

      const result = await writeFileTool.execute({
        path: './new-file.txt',
        content: 'content',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('需要用户确认');
      expect(result.error).toContain('首次写入此文件');
    });
  });

  describe('错误处理', () => {
    it('文件系统错误返回错误信息', async () => {
      vi.mocked(fs.writeFile).mockRejectedValue(new Error('EACCES: permission denied'));

      const result = await writeFileTool.execute({
        path: './test.txt',
        content: 'content',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('EACCES');
    });

    it('目录创建失败返回错误信息', async () => {
      vi.mocked(fs.mkdir).mockImplementationOnce(() => {
        return Promise.reject(new Error('mkdir failed'));
      });

      const result = await writeFileTool.execute({
        path: './deep/nested/file.txt',
        content: 'content',
      });

      expect(result.success).toBe(false);
      // The error is now wrapped by the editors module
      expect(result.error).toBeTruthy();
    });

    it('非 Error 对象也能正确处理', async () => {
      vi.mocked(fs.writeFile).mockRejectedValue('String error');

      const result = await writeFileTool.execute({
        path: './test.txt',
        content: 'content',
      });

      expect(result.success).toBe(false);
      expect(result.error).toContain('String error');
    });
  });

  describe('工具元数据', () => {
    it('包含完整的元数据', () => {
      expect(writeFileTool.metadata.category).toBe('filesystem');
      expect(writeFileTool.metadata.keywords).toContain('write');
      expect(writeFileTool.metadata.keywords).toContain('save');
      expect(writeFileTool.metadata.keywords).toContain('create');
      expect(writeFileTool.metadata.keywords).toContain('写入');
      expect(writeFileTool.metadata.deferLoading).toBe(false);
    });

    it('参数定义正确', () => {
      expect(writeFileTool.parameters.path.required).toBe(true);
      expect(writeFileTool.parameters.content.required).toBe(true);
      expect(writeFileTool.parameters.path.type).toBe('string');
      expect(writeFileTool.parameters.content.type).toBe('string');
    });
  });
});
