import { describe, it, expect, beforeEach, vi } from 'vitest';
import { LSPClientManager } from '../../../src/lsp/client.js';
import { DiagnosticSeverity } from 'vscode-languageserver-protocol';

// Mock child_process
const mockSpawn = vi.fn();
const mockExecSync = vi.fn();

vi.mock('child_process', () => ({
  spawn: (...args: unknown[]) => mockSpawn(...args),
  execSync: (...args: unknown[]) => mockExecSync(...args),
}));

// Mock vscode-jsonrpc
const mockConnection = {
  listen: vi.fn(),
  sendRequest: vi.fn().mockResolvedValue({}),
  sendNotification: vi.fn(),
  onNotification: vi.fn(),
  dispose: vi.fn(),
};

vi.mock('vscode-jsonrpc/node.js', () => ({
  createMessageConnection: vi.fn(() => mockConnection),
  StreamMessageReader: vi.fn(),
  StreamMessageWriter: vi.fn(),
}));

// Mock fs/promises
vi.mock('fs/promises', () => ({
  readFile: vi.fn().mockResolvedValue('file content'),
}));

// Mock language module
vi.mock('../../../src/lsp/language.js', () => ({
  getLanguageId: vi.fn((path: string) => {
    if (path.endsWith('.ts')) return 'typescript';
    if (path.endsWith('.py')) return 'python';
    return undefined;
  }),
}));

// Mock server module
vi.mock('../../../src/lsp/server.js', () => ({
  getServerConfig: vi.fn((languageId: string) => {
    if (languageId === 'typescript') {
      return {
        command: 'typescript-language-server',
        args: ['--stdio'],
        env: {},
        initializationOptions: {},
      };
    }
    if (languageId === 'python') {
      return {
        command: 'pylsp',
        args: [],
        env: {},
      };
    }
    return null;
  }),
}));

import { getLanguageId } from '../../../src/lsp/language.js';

describe('LSPClientManager - 扩展测试', () => {
  let manager: LSPClientManager;

  beforeEach(() => {
    vi.clearAllMocks();
    manager = new LSPClientManager('/test/project');

    // 默认命令存在
    mockExecSync.mockReturnValue(Buffer.from(''));

    // 模拟 spawn 返回的进程
    mockSpawn.mockReturnValue({
      stdin: { on: vi.fn(), write: vi.fn() },
      stdout: { on: vi.fn(), pipe: vi.fn() },
      stderr: { on: vi.fn() },
      on: vi.fn(),
      kill: vi.fn(),
      pid: 12345,
    });
  });

  describe('getClient - 启动客户端', () => {
    it('重复获取同一语言返回相同客户端', async () => {
      const client1 = await manager.getClient('typescript');
      const client2 = await manager.getClient('typescript');

      expect(client1).toBe(client2);
    });

    it('成功启动时设置初始化状态', async () => {
      const client = await manager.getClient('typescript');

      if (client) {
        expect(client.initialized).toBe(true);
        expect(client.languageId).toBe('typescript');
      }
    });
  });

  describe('touchFile - 文件变更通知', () => {
    it('相对路径转换为绝对路径', async () => {
      vi.mocked(getLanguageId).mockReturnValue('typescript');

      // 先获取客户端
      await manager.getClient('typescript');

      // touchFile 应该能处理相对路径
      const result = await manager.touchFile('relative/file.ts');

      // 由于客户端已初始化，应该调用通知
      expect(typeof result).toBe('boolean');
    });

    it('首次启动返回 true', async () => {
      vi.mocked(getLanguageId).mockReturnValue('typescript');

      const result = await manager.touchFile('/test/file.ts');

      // 首次启动服务器应返回 true
      expect(result).toBe(true);
    });

    it('后续调用返回 false', async () => {
      vi.mocked(getLanguageId).mockReturnValue('typescript');

      // 首次调用
      await manager.touchFile('/test/file.ts');

      // 第二次调用
      const result = await manager.touchFile('/test/file.ts');

      // 已经在运行，返回 false
      expect(result).toBe(false);
    });
  });

  describe('closeFile - 关闭文件', () => {
    it('正常关闭打开的文件', async () => {
      vi.mocked(getLanguageId).mockReturnValue('typescript');

      // 先打开文件
      await manager.touchFile('/test/file.ts');

      // 关闭文件
      await manager.closeFile('/test/file.ts');

      expect(mockConnection.sendNotification).toHaveBeenCalledWith(
        'textDocument/didClose',
        expect.any(Object)
      );
    });
  });

  describe('诊断信息转换', () => {
    it('正确转换诊断严重性', async () => {
      // 通过 onNotification 回调模拟诊断
      let diagnosticsCallback: ((params: unknown) => void) | null = null;
      mockConnection.onNotification.mockImplementation((method: string, callback: (params: unknown) => void) => {
        if (method === 'textDocument/publishDiagnostics') {
          diagnosticsCallback = callback;
        }
      });

      vi.mocked(getLanguageId).mockReturnValue('typescript');
      await manager.getClient('typescript');

      // 模拟收到诊断
      if (diagnosticsCallback) {
        diagnosticsCallback({
          uri: 'file:///test/file.ts',
          diagnostics: [
            {
              range: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } },
              severity: DiagnosticSeverity.Error,
              message: 'Test error',
              source: 'typescript',
              code: 'TS2345',
            },
            {
              range: { start: { line: 1, character: 0 }, end: { line: 1, character: 5 } },
              severity: DiagnosticSeverity.Warning,
              message: 'Test warning',
            },
            {
              range: { start: { line: 2, character: 0 }, end: { line: 2, character: 5 } },
              severity: DiagnosticSeverity.Information,
              message: 'Test info',
            },
            {
              range: { start: { line: 3, character: 0 }, end: { line: 3, character: 5 } },
              severity: DiagnosticSeverity.Hint,
              message: 'Test hint',
            },
          ],
        });
      }

      // 获取诊断
      const diagnostics = manager.getFileDiagnostics('/test/file.ts');

      expect(diagnostics.length).toBe(4);
      expect(diagnostics[0].severity).toBe('error');
      expect(diagnostics[1].severity).toBe('warning');
      expect(diagnostics[2].severity).toBe('info');
      expect(diagnostics[3].severity).toBe('hint');
    });

    it('转换行列号（从 0 开始转为 1 开始）', async () => {
      let diagnosticsCallback: ((params: unknown) => void) | null = null;
      mockConnection.onNotification.mockImplementation((method: string, callback: (params: unknown) => void) => {
        if (method === 'textDocument/publishDiagnostics') {
          diagnosticsCallback = callback;
        }
      });

      vi.mocked(getLanguageId).mockReturnValue('typescript');
      await manager.getClient('typescript');

      if (diagnosticsCallback) {
        diagnosticsCallback({
          uri: 'file:///test/file.ts',
          diagnostics: [
            {
              range: { start: { line: 9, character: 4 }, end: { line: 9, character: 20 } },
              severity: DiagnosticSeverity.Error,
              message: 'Error',
            },
          ],
        });
      }

      const diagnostics = manager.getFileDiagnostics('/test/file.ts');

      expect(diagnostics[0].line).toBe(10); // 9 + 1
      expect(diagnostics[0].column).toBe(5); // 4 + 1
      expect(diagnostics[0].endLine).toBe(10);
      expect(diagnostics[0].endColumn).toBe(21);
    });
  });

  describe('shutdown - 关闭所有客户端', () => {
    it('关闭所有运行中的客户端', async () => {
      vi.mocked(getLanguageId).mockImplementation((path: string) => {
        if (path.endsWith('.ts')) return 'typescript';
        if (path.endsWith('.py')) return 'python';
        return undefined;
      });

      // 启动多个客户端
      await manager.getClient('typescript');
      await manager.getClient('python');

      expect(manager.getRunningServers().length).toBe(2);

      // 关闭
      await manager.shutdown();

      expect(manager.getRunningServers().length).toBe(0);
    });

    it('处理关闭时的错误', async () => {
      vi.mocked(getLanguageId).mockReturnValue('typescript');
      await manager.getClient('typescript');

      // 模拟 dispose 抛出错误
      mockConnection.dispose.mockImplementation(() => {
        throw new Error('Dispose error');
      });

      // 应该不抛出错误
      await expect(manager.shutdown()).resolves.not.toThrow();
    });
  });

  describe('进程事件处理', () => {
    it('进程退出时清理客户端', async () => {
      let exitCallback: (() => void) | null = null;
      mockSpawn.mockReturnValue({
        stdin: { on: vi.fn(), write: vi.fn() },
        stdout: { on: vi.fn(), pipe: vi.fn() },
        stderr: { on: vi.fn() },
        on: vi.fn((event: string, callback: () => void) => {
          if (event === 'exit') {
            exitCallback = callback;
          }
        }),
        kill: vi.fn(),
        pid: 12345,
      });

      vi.mocked(getLanguageId).mockReturnValue('typescript');
      await manager.getClient('typescript');

      expect(manager.isServerRunning('typescript')).toBe(true);

      // 模拟进程退出
      if (exitCallback) {
        exitCallback();
      }

      expect(manager.isServerRunning('typescript')).toBe(false);
    });
  });

  describe('getDiagnostics - 获取所有诊断', () => {
    it('可以过滤指定文件的诊断', async () => {
      let diagnosticsCallback: ((params: unknown) => void) | null = null;
      mockConnection.onNotification.mockImplementation((method: string, callback: (params: unknown) => void) => {
        if (method === 'textDocument/publishDiagnostics') {
          diagnosticsCallback = callback;
        }
      });

      vi.mocked(getLanguageId).mockReturnValue('typescript');
      await manager.getClient('typescript');

      // 为两个文件添加诊断
      if (diagnosticsCallback) {
        diagnosticsCallback({
          uri: 'file:///test/file1.ts',
          diagnostics: [
            {
              range: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } },
              severity: DiagnosticSeverity.Error,
              message: 'Error in file1',
            },
          ],
        });
        diagnosticsCallback({
          uri: 'file:///test/file2.ts',
          diagnostics: [
            {
              range: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } },
              severity: DiagnosticSeverity.Warning,
              message: 'Warning in file2',
            },
          ],
        });
      }

      // 获取所有诊断
      const allDiagnostics = manager.getDiagnostics();
      expect(allDiagnostics.size).toBe(2);

      // 只获取 file1 的诊断
      const file1Diagnostics = manager.getDiagnostics('/test/file1.ts');
      expect(file1Diagnostics.size).toBe(1);
      expect(file1Diagnostics.has('/test/file1.ts')).toBe(true);
    });
  });
});
