import { HttpException, HttpStatus } from '@nestjs/common';
import { McpTransportController } from '../mcp-transport.controller';

// Mock SSEServerTransport as a class
vi.mock('@goodgo/mcp-servers', () => {
  return {
    SSEServerTransport: class MockSSEServerTransport {
      sessionId = 'mock-session-id';
      handlePostMessage = vi.fn().mockResolvedValue(undefined);
      constructor(public path: string, public res: unknown) {}
    },
  };
});

describe('McpTransportController', () => {
  let controller: McpTransportController;
  let mockRegistry: {
    getServerNames: ReturnType<typeof vi.fn>;
    getServer: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    vi.clearAllMocks();
    mockRegistry = {
      getServerNames: vi.fn(),
      getServer: vi.fn(),
    };
    controller = new McpTransportController(mockRegistry as any);
  });

  describe('security decorators', () => {
    it('has JwtAuthGuard applied at controller level', () => {
      const guards = Reflect.getMetadata('__guards__', McpTransportController);
      expect(guards).toBeDefined();
      expect(guards).toHaveLength(1);
      // The guard is applied via @UseGuards(JwtAuthGuard)
      const guardNames = guards.map((g: any) => g.name || g.constructor?.name);
      expect(guardNames).toContain('JwtAuthGuard');
    });

    it('has Throttle metadata on listServers endpoint', () => {
      const limit = Reflect.getMetadata(
        'THROTTLER:LIMITdefault',
        McpTransportController.prototype.listServers,
      );
      expect(limit).toBe(30);
    });

    it('has Throttle metadata on handleSse endpoint with low limit', () => {
      const limit = Reflect.getMetadata(
        'THROTTLER:LIMITdefault',
        McpTransportController.prototype.handleSse,
      );
      expect(limit).toBe(5);
    });

    it('has Throttle metadata on handleMessage endpoint', () => {
      const limit = Reflect.getMetadata(
        'THROTTLER:LIMITdefault',
        McpTransportController.prototype.handleMessage,
      );
      expect(limit).toBe(30);
    });
  });

  describe('listServers', () => {
    it('returns list of server names from registry', () => {
      mockRegistry.getServerNames.mockReturnValue(['search', 'listings']);

      const result = controller.listServers();

      expect(result).toEqual({ servers: ['search', 'listings'] });
      expect(mockRegistry.getServerNames).toHaveBeenCalledOnce();
    });

    it('returns empty array when no servers registered', () => {
      mockRegistry.getServerNames.mockReturnValue([]);

      const result = controller.listServers();

      expect(result).toEqual({ servers: [] });
    });
  });

  describe('handleSse', () => {
    const mockUser = { sub: 'user-1', email: 'test@example.com' };
    let mockReq: { on: ReturnType<typeof vi.fn> };
    let mockRes: Record<string, unknown>;

    beforeEach(() => {
      mockReq = { on: vi.fn() };
      mockRes = {};
    });

    it('throws NOT_FOUND when server does not exist', async () => {
      mockRegistry.getServer.mockReturnValue(null);

      await expect(
        controller.handleSse('nonexistent', mockUser as any, mockReq as any, mockRes as any),
      ).rejects.toThrow(HttpException);

      try {
        await controller.handleSse('nonexistent', mockUser as any, mockReq as any, mockRes as any);
      } catch (error) {
        expect((error as HttpException).getStatus()).toBe(HttpStatus.NOT_FOUND);
        expect((error as HttpException).message).toContain('nonexistent');
      }
    });

    it('creates transport and connects to server', async () => {
      const mockServer = { connect: vi.fn().mockResolvedValue(undefined) };
      mockRegistry.getServer.mockReturnValue(mockServer);

      await controller.handleSse('search', mockUser as any, mockReq as any, mockRes as any);

      expect(mockRegistry.getServer).toHaveBeenCalledWith('search');
      expect(mockServer.connect).toHaveBeenCalledOnce();
      expect(mockReq.on).toHaveBeenCalledWith('close', expect.any(Function));
    });

    it('cleans up transport on connection close', async () => {
      const mockServer = { connect: vi.fn().mockResolvedValue(undefined) };
      mockRegistry.getServer.mockReturnValue(mockServer);

      await controller.handleSse('search', mockUser as any, mockReq as any, mockRes as any);

      // Simulate connection close
      const closeHandler = mockReq.on.mock.calls[0]![1] as () => void;
      closeHandler();

      // Transport should be removed — subsequent message calls should fail
      const msgReq = { query: { sessionId: 'mock-session-id' } } as any;
      const msgRes = {} as any;
      await expect(
        controller.handleMessage('search', mockUser as any, msgReq, msgRes),
      ).rejects.toThrow(HttpException);
    });
  });

  describe('handleMessage', () => {
    const mockUser = { sub: 'user-1', email: 'test@example.com' };

    it('throws BAD_REQUEST when sessionId query parameter is missing', async () => {
      const mockReq = { query: {} } as any;
      const mockRes = {} as any;

      await expect(
        controller.handleMessage('search', mockUser as any, mockReq, mockRes),
      ).rejects.toThrow(HttpException);

      try {
        await controller.handleMessage('search', mockUser as any, mockReq, mockRes);
      } catch (error) {
        expect((error as HttpException).getStatus()).toBe(HttpStatus.BAD_REQUEST);
      }
    });

    it('throws NOT_FOUND when session does not exist', async () => {
      const mockReq = { query: { sessionId: 'nonexistent-session' } } as any;
      const mockRes = {} as any;

      await expect(
        controller.handleMessage('search', mockUser as any, mockReq, mockRes),
      ).rejects.toThrow(HttpException);

      try {
        await controller.handleMessage('search', mockUser as any, mockReq, mockRes);
      } catch (error) {
        expect((error as HttpException).getStatus()).toBe(HttpStatus.NOT_FOUND);
      }
    });
  });
});
