// This program has been developed by students from the bachelor Computer Science at Utrecht
// University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences)
import { renderHook, act, cleanup } from '@testing-library/react';
import { 
    sendAPICall, 
    nextPhase,  
    pauseExperiment, 
    playExperiment, 
    useExperimentLogger, 
    useStatusLogger 
} from '../../../src/pages/MonitoringPage/MonitoringPageAPI';
import { API_BASE_URL } from '../../../src/config/api.ts';

// --- MOCK EVENT SOURCE SETUP ---
// This mocks the browser's EventSource so we can manually 'push' messages to our hooks
const mockInstances: MockEventSource[] = [];

class MockEventSource {
  url: string;
  onmessage: ((event: MessageEvent) => void) | null = null;
  onerror: ((event: Event) => void) | null = null; // Added onerror support
  closed = false;

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

  sendMessage(data: string) {
    if (this.onmessage) {
      this.onmessage({ data } as MessageEvent);
    }
  }

  triggerError(err: any) {
    if (this.onerror) {
        this.onerror(err);
    }
  }

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

// Mock global EventSource
beforeAll(() => {
  (globalThis as any).EventSource = jest.fn((url: string) => new MockEventSource(url));
});

// Mock global fetch
beforeEach(() => {
  globalThis.fetch = jest.fn(() =>
    Promise.resolve({
      ok: true,
      json: () => Promise.resolve({ reply: 'ok' }),
    })
  ) as jest.Mock;
});

// Cleanup after every test
afterEach(() => {
  cleanup();
  jest.restoreAllMocks();
  mockInstances.length = 0;
});

describe('MonitoringPageAPI', () => {

  describe('sendAPICall', () => {
    test('sends correct POST request', async () => {
      await sendAPICall('test_type', 'test_ctx');

      expect(globalThis.fetch).toHaveBeenCalledWith(
        `${API_BASE_URL}/button_pressed`,
        expect.objectContaining({
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ type: 'test_type', context: 'test_ctx' }),
        })
      );
    });

    test('appends endpoint if provided', async () => {
      await sendAPICall('t', 'c', '/extra');

      expect(globalThis.fetch).toHaveBeenCalledWith(
        expect.stringContaining('/button_pressed/extra'),
        expect.any(Object)
      );
    });

    test('logs error on fetch network failure', async () => {
      (globalThis.fetch as jest.Mock).mockRejectedValue('Network error');
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

      await sendAPICall('t', 'c');

      expect(consoleSpy).toHaveBeenCalledWith('Failed to send api call:', 'Network error');
    });

    test('throws error if response is not ok', async () => {
        (globalThis.fetch as jest.Mock).mockResolvedValue({ ok: false });
        const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

        await sendAPICall('t', 'c');
        
        expect(consoleSpy).toHaveBeenCalledWith('Failed to send api call:', expect.any(Error));
    });
  });

  describe('Helper Functions', () => {
    test('nextPhase sends correct params', async () => {
      await nextPhase();
      expect(globalThis.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({ body: JSON.stringify({ type: 'next_phase', context: '' }) })
      );
    });

    test('pauseExperiment sends correct params', async () => {
      await pauseExperiment();
      expect(globalThis.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({ body: JSON.stringify({ type: 'pause', context: 'true' }) })
      );
    });

    test('playExperiment sends correct params', async () => {
      await playExperiment();
      expect(globalThis.fetch).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({ body: JSON.stringify({ type: 'pause', context: 'false' }) })
      );
    });
  });

  describe('useExperimentLogger', () => {
    test('connects to SSE and receives messages', () => {
      const onUpdate = jest.fn();
      
      // Hook must be rendered to start the effect
      renderHook(() => useExperimentLogger(onUpdate));

      // Retrieve the mocked instance created by the hook
      const eventSource = mockInstances[0];
      expect(eventSource.url).toContain('/experiment_stream');

      // Simulate incoming message
      act(() => {
        eventSource.sendMessage(JSON.stringify({ type: 'phase_update', id: '1' }));
      });

      expect(onUpdate).toHaveBeenCalledWith({ type: 'phase_update', id: '1' });
    });

    test('handles JSON parse errors in stream', () => {
      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
      renderHook(() => useExperimentLogger());
      const eventSource = mockInstances[0];

      act(() => {
        eventSource.sendMessage('invalid-json');
      });

      expect(consoleSpy).toHaveBeenCalledWith('Stream parse error:', expect.any(Error));
    });
    
    

    test('handles SSE connection error', () => {
        const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
        renderHook(() => useExperimentLogger());
        const eventSource = mockInstances[0];
  
        act(() => {
          eventSource.triggerError('Connection lost');
        });
  
        expect(consoleSpy).toHaveBeenCalledWith('SSE Connection Error:', 'Connection lost');
        expect(eventSource.closed).toBe(true);
      });

    test('closes EventSource on unmount', () => {
      const { unmount } = renderHook(() => useExperimentLogger());
      const eventSource = mockInstances[0];
      const closeSpy = jest.spyOn(eventSource, 'close');

      unmount();

      expect(closeSpy).toHaveBeenCalled();
      expect(eventSource.closed).toBe(true);
    });
  });

  describe('useStatusLogger', () => {
    test('connects to SSE and receives messages', () => {
      const onUpdate = jest.fn();
      renderHook(() => useStatusLogger(onUpdate));
      const eventSource = mockInstances[0];

      expect(eventSource.url).toContain('/status_stream');

      act(() => {
        eventSource.sendMessage(JSON.stringify({ some: 'data' }));
      });

      expect(onUpdate).toHaveBeenCalledWith({ some: 'data' });
    });

    test('handles JSON parse errors', () => {
      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
      renderHook(() => useStatusLogger());
      const eventSource = mockInstances[0];

      act(() => {
        eventSource.sendMessage('bad-data');
      });

      expect(consoleSpy).toHaveBeenCalledWith('Status stream error:', expect.any(Error));
    });
  });
});
