// 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 React from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react';
import '@testing-library/jest-dom';

// Corrected Imports
import { 
  GestureControls, 
  SpeechPresets, 
  DirectSpeechInput, 
  StatusList, 
  RobotConnected 
} from '../../../src/pages/MonitoringPage/MonitoringPageComponents';

import * as MonitoringAPI from '../../../src/pages/MonitoringPage/MonitoringPageAPI';

// Mock the API Call function with the correct path
jest.mock('../../../src/pages/MonitoringPage/MonitoringPageAPI', () => ({
  sendAPICall: jest.fn(),
}));

describe('MonitoringPageComponents', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('GestureControls', () => {
    test('renders and sends gesture command', () => {
      render(<GestureControls />);
      
      fireEvent.change(screen.getByRole('combobox'), { 
        target: { value: 'animations/Stand/Gestures/Hey_1' } 
      });
      
      // Click button
      fireEvent.click(screen.getByText('Actuate'));
      
      // Expect the API to be called with that new value
      expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('gesture', 'animations/Stand/Gestures/Hey_1');
    });
  });

  describe('SpeechPresets', () => {
    test('renders buttons and sends speech command', () => {
      render(<SpeechPresets />);
      
      const btn = screen.getByText('"Hello, I\'m Pepper"');
      fireEvent.click(btn);
      
      expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('speech', "Hello, I'm Pepper");
    });
  });

  describe('DirectSpeechInput', () => {
    test('inputs text and sends on button click', () => {
      render(<DirectSpeechInput />);
      const input = screen.getByPlaceholderText('Type message...');
      
      fireEvent.change(input, { target: { value: 'Custom text' } });
      fireEvent.click(screen.getByText('Send'));
      
      expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('speech', 'Custom text');
      expect(input).toHaveValue(''); // Should clear
    });

    test('sends on Enter key', () => {
        render(<DirectSpeechInput />);
        const input = screen.getByPlaceholderText('Type message...');
        
        fireEvent.change(input, { target: { value: 'Enter text' } });
        fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
        
        expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('speech', 'Enter text');
      });
  
      test('does not send empty text', () => {
        render(<DirectSpeechInput />);
        fireEvent.click(screen.getByText('Send'));
        expect(MonitoringAPI.sendAPICall).not.toHaveBeenCalled();
      });
  });

  describe('StatusList', () => {
    const mockSet = jest.fn();
    const items = [
      { id: '1', name: 'Item 1' },
      { id: '2', name: 'Item 2' }
    ];

    test('renders list items', () => {
      render(<StatusList title="Test List" items={items} type="goal" activeIds={{}} />);
      expect(screen.getByText('Test List')).toBeInTheDocument();
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    test('Goals: click override on inactive item calls API', () => {
      render(
        <StatusList 
          title="Goals" 
          items={items} 
          type="goal" 
          activeIds={{}} 
          setActiveIds={mockSet} 
        />
      );
      
      // Click the X (inactive)
      const indicator = screen.getAllByText('❌')[0];
      fireEvent.click(indicator);
      
      expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('override', '1');
      expect(mockSet).toHaveBeenCalled();
    });

    test('Conditional Norms: click override on ACTIVE item unachieves', () => {
        render(
          <StatusList 
            title="CN" 
            items={items} 
            type="cond_norm" 
            activeIds={{ '1': true }} 
          />
        );
        
        const indicator = screen.getByText('✔️'); // It is active
        fireEvent.click(indicator);
        
        expect(MonitoringAPI.sendAPICall).toHaveBeenCalledWith('override_unachieve', '1');
      });
  
      test('Current Goal highlighting', () => {
          render(
              <StatusList 
                title="Goals" 
                items={items} 
                type="goal" 
                activeIds={{}} 
                currentGoalIndex={0}
              />
            );
          // Using regex to handle the "(Current)" text
          expect(screen.getByText(/Item 1/)).toBeInTheDocument();
          expect(screen.getByText(/(Current)/)).toBeInTheDocument();
      });
  });

  describe('RobotConnected', () => {
    let mockEventSource: any;

    beforeAll(() => {
        Object.defineProperty(window, 'EventSource', {
            writable: true,
            value: jest.fn().mockImplementation(() => ({
                close: jest.fn(),
                onmessage: null,
            })),
        });
    });

    beforeEach(() => {
        mockEventSource = new window.EventSource('url');
        (window.EventSource as unknown as jest.Mock).mockClear();
        (window.EventSource as unknown as jest.Mock).mockImplementation(() => mockEventSource);
    });

    test('displays disconnected initially', () => {
        render(<RobotConnected />);
        expect(screen.getByText('● Robot is disconnected')).toBeInTheDocument();
    });

    test('updates to connected when SSE receives true', async () => {
        render(<RobotConnected />);
        
        act(() => {
            if(mockEventSource.onmessage) {
                mockEventSource.onmessage({ data: 'true' } as MessageEvent);
            }
        });

        expect(await screen.findByText('● Robot is connected')).toBeInTheDocument();
    });
    
    test('handles invalid JSON gracefully', async () => {
        const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
        render(<RobotConnected />);
        
        act(() => {
            if(mockEventSource.onmessage) {
                mockEventSource.onmessage({ data: 'invalid-json' } as MessageEvent);
            }
        });
        
        // Should catch error and log it, state remains disconnected
        expect(consoleSpy).toHaveBeenCalledWith('Ping message not in correct format:', 'invalid-json');
        consoleSpy.mockRestore();
    });

    test('logs error if state update fails (inner catch block)', async () => {
        const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
        
        // 1. Force useState to return a setter that throws an error
        const mockThrowingSetter = jest.fn(() => { throw new Error('Forced State Error'); });
        
        // We use mockImplementation to return [currentState, throwingSetter]
        const useStateSpy = jest.spyOn(React, 'useState')
            .mockImplementation(() => [null, mockThrowingSetter]);

        render(<RobotConnected />);
        
        // 2. Trigger the event with VALID JSON ("true")
        // This passes the first JSON.parse try/catch, 
        // but fails when calling setConnected(true) because of our mock.
        await act(async () => {
            if (mockEventSource.onmessage) {
                mockEventSource.onmessage({ data: 'true' } as MessageEvent);
            }
        });

        // 3. Verify the specific error log from line 205
        expect(consoleSpy).toHaveBeenCalledWith("couldnt extract connected from incoming ping data");
        
        // Cleanup spies
        useStateSpy.mockRestore();
        consoleSpy.mockRestore();
    });
  });
});
