import { render, screen, act, cleanup, waitFor } from '@testing-library/react';
import ConnectedRobots from '../../../src/pages/ConnectedRobots/ConnectedRobots';

// Mock event source
const mockInstances: MockEventSource[] = [];
class MockEventSource {
  url: string;
  onmessage: ((event: MessageEvent) => void) | null = null;
  closed = false;

  constructor(url: string) {
    this.url = url;
    mockInstances.push(this);
  }

  sendMessage(data: string) {
    // Trigger whatever the component listens to
    this.onmessage?.({ data } as MessageEvent);
  }

  close() {
    this.closed = true;
  }
}

// mock event source generation with fake function that returns our fake mock source
beforeAll(() => {
  // Cast globalThis to a type exposing EventSource and assign a mocked constructor.
  (globalThis as unknown as { EventSource?: typeof EventSource }).EventSource =
    jest.fn((url: string) => new MockEventSource(url)) as unknown as typeof EventSource;
});

// clean after tests
afterEach(() => {
  cleanup();
  jest.restoreAllMocks();
  mockInstances.length = 0;
});

describe('ConnectedRobots', () => {
  test('renders initial state correctly', () => {
    render(<ConnectedRobots />);
    
    // Check initial texts (before connection)
    expect(screen.getByText('Is robot currently connected?')).toBeInTheDocument();
    expect(screen.getByText(/Robot is currently:\s*checking/i)).toBeInTheDocument();
    expect(
      screen.getByText(/If checking continues, make sure CB is properly loaded/i)
    ).toBeInTheDocument();
  });

  test('updates to connected when message data is true', async () => {
    render(<ConnectedRobots />);
    const eventSource = mockInstances[0];
    expect(eventSource).toBeDefined();

    // Check state after getting 'true' message
    await act(async () => {
      eventSource.sendMessage('true');
    });

    await waitFor(() => {
      expect(screen.getByText(/connected! 🟢/i)).toBeInTheDocument();
    });
  });

  test('updates to not connected when message data is false', async () => {
    render(<ConnectedRobots />);
    const eventSource = mockInstances[0];

    // Check statew after getting 'false' message
    await act(async () => {
      eventSource.sendMessage('false');
    });

    await waitFor(() => {
      expect(screen.getByText(/not connected.*🔴/i)).toBeInTheDocument();
    });
  });

  test('handles invalid JSON gracefully', async () => {
    const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
    render(<ConnectedRobots />);
    const eventSource = mockInstances[0];

    await act(async () => {
      eventSource.sendMessage('not-json');
    });

    expect(logSpy).toHaveBeenCalledWith(
      'Ping message not in correct format:',
      'not-json'
    );
  });

  test('closes EventSource on unmount', () => {
    render(<ConnectedRobots />);
    const eventSource = mockInstances[0];
    const closeSpy = jest.spyOn(eventSource, 'close');
    cleanup(); 
    expect(closeSpy).toHaveBeenCalled();
    expect(eventSource.closed).toBe(true);
  });
});