// 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)
// BasicBeliefNode.test.tsx
import { describe, it, beforeEach } from '@jest/globals';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
import BasicBeliefNode, { type BasicBeliefNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx';
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
import type { Node } from '@xyflow/react';
import '@testing-library/jest-dom';

describe('BasicBeliefNode', () => {
  let user: ReturnType<typeof userEvent.setup>;

  beforeEach(() => {
    user = userEvent.setup();
  });
  describe('Rendering', () => {
    it('should render the basic belief node with keyword type by default', () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'help', value: 'help', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      expect(screen.getByText('Belief:')).toBeInTheDocument();
      expect(screen.getByDisplayValue('Keyword said:')).toBeInTheDocument();
      expect(screen.getByDisplayValue('help')).toBeInTheDocument();
    });

    it('should render with semantic belief type', () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-2',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'semantic', id: 'test', value: 'test value', description: "test description", label: 'Detected with LLM:' },
          hasReduce: true,
        },
      };

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      expect(screen.getByDisplayValue('Detected with LLM:')).toBeInTheDocument();
      expect(screen.getByDisplayValue('test value')).toBeInTheDocument();
    });

    it('should render with object belief type', () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-3',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'object', id: 'obj1', value: 'cup', label: 'Object found:' },
          hasReduce: true,
        },
      };

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      expect(screen.getByDisplayValue('Object found:')).toBeInTheDocument();
      expect(screen.getByDisplayValue('cup')).toBeInTheDocument();
    });

    it('should render with emotion belief type and select dropdown', () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-4',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'emotion', id: 'em1', value: 'happy', label: 'Emotion recognised:' },
          hasReduce: true,
        },
      };

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      expect(screen.getByDisplayValue('Emotion recognised:')).toBeInTheDocument();
      // For emotion type, we should check that the select has the correct value selected
      const selectElement = screen.getByDisplayValue('happy');
      expect(selectElement).toBeInTheDocument();
      expect((selectElement as HTMLSelectElement).value).toBe('happy');
    });

    it('should render emotion dropdown with all emotion options', () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-5',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'emotion', id: 'em1', value: 'happy', label: 'Emotion recognised:' },
          hasReduce: true,
        },
      };

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const selectElement = screen.getByDisplayValue('happy');
      expect(selectElement).toBeInTheDocument();
      
      // Check that all emotion options are present
      expect(screen.getByText('happy')).toBeInTheDocument();
      expect(screen.getByText('angry')).toBeInTheDocument();
      expect(screen.getByText('sad')).toBeInTheDocument();
      expect(screen.getByText('surprise')).toBeInTheDocument();
    });

    it('should render without wrapping quotes for object type', () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-6',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'object', id: 'obj1', value: 'chair', label: 'Object found:' },
          hasReduce: true,
        },
      };

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      // Object type should not have wrapping quotes
      const inputs = screen.getAllByDisplayValue('chair');
      expect(inputs.length).toBe(1); // Only the text input, no extra quote elements
    });
  });

  describe('User Interactions', () => {
    it('should update belief type when select is changed', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: 'hello', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const select = screen.getByDisplayValue('Keyword said:');
      await user.selectOptions(select, 'semantic');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        expect(updatedNode?.data.belief.type).toBe('semantic');
        // Note: The component doesn't update the label when changing type
        // So we can't test for label change
      });
    });

    it('should update text value when typing for keyword type', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: '', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const input = screen.getByPlaceholderText('keyword...');
      await user.type(input, 'help me{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        expect(updatedNode?.data.belief.value).toBe('help me');
      });
    });

    it('should update text value when typing for semantic type', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'semantic', id: 'test', value: 'test value', description: "test description", label: 'Detected with LLM:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const input = screen.getByDisplayValue('test value') as HTMLInputElement;
      
      // Clear the input
      for (let i = 0; i < 'test value'.length; i++) {
        await user.type(input, '{backspace}');
      }
      await user.type(input, 'new semantic value{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        expect(updatedNode?.data.belief.value).toBe('new semantic value');
      });
    });

    it('should update emotion value when selecting from dropdown', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'emotion', id: 'em1', value: 'sad', label: 'Emotion recognised:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const select = screen.getByDisplayValue('sad');
      await user.selectOptions(select, 'happy');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        expect(updatedNode?.data.belief.value).toBe('happy');
      });
    });

    it('should preserve value when switching between text-based belief types', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: 'test value', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      // Switch from keyword to semantic
      const typeSelect = screen.getByDisplayValue('Keyword said:');
      await user.selectOptions(typeSelect, 'semantic');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        expect(updatedNode?.data.belief.type).toBe('semantic');
        expect(updatedNode?.data.belief.value).toBe('test value'); // Value should be preserved
      });
    });

    it('should automatically choose the first option when switching to emotion type, and carry on to the text values', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: 'some text', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      // Switch from keyword to emotion
      const typeSelect = screen.getByDisplayValue('Keyword said:');
      await user.selectOptions(typeSelect, 'emotion');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        expect(updatedNode?.data.belief.type).toBe('emotion');
        // The component doesn't reset the value when changing types
        // So it keeps the old value even though it doesn't make sense for emotion type
        expect(updatedNode?.data.belief.value).toBe('sad');
      });
    });
  });

  describe('Integration with Store', () => {
    it('should properly update the store when changing belief value', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: '', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const input = screen.getByPlaceholderText('keyword...');
      await user.type(input, 'emergency{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        expect(state.nodes).toHaveLength(1);
        expect(state.nodes[0].id).toBe('belief-1');
        const beliefData = state.nodes[0].data as BasicBeliefNodeData;
        expect(beliefData.belief.value).toBe('emergency');
        expect(beliefData.belief.type).toBe('keyword');
      });
    });

    it('should properly update the store when changing belief type', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: 'test', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const select = screen.getByDisplayValue('Keyword said:');
      await user.selectOptions(select, 'object');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const beliefData = state.nodes[0].data as BasicBeliefNodeData;
        expect(beliefData.belief.type).toBe('object');
        // Note: The component doesn't update the label when changing type
        expect(beliefData.belief.value).toBe('test'); // Value should be preserved
      });
    });

    it('should not affect other nodes when updating one belief node', async () => {
      const belief1: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief 1',
          droppable: true,
          belief: { type: 'keyword', id: 'kw1', value: 'hello', label: 'Keyword said:' },
          hasReduce: true,
        },
      };

      const belief2: Node<BasicBeliefNodeData> = {
        id: 'belief-2',
        type: 'basic_belief',
        position: { x: 100, y: 0 },
        data: {
          label: 'Belief 2',
          droppable: true,
          belief: { type: 'object', id: 'obj1', value: 'chair', label: 'Object found:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [belief1, belief2],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={belief1.id}
          type={belief1.type as string}
          data={belief1.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const input = screen.getByDisplayValue('hello') as HTMLInputElement;
      
      // Clear the input
      for (let i = 0; i < 'hello'.length; i++) {
        await user.type(input, '{backspace}');
      }
      await user.type(input, 'goodbye{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedBelief1 = state.nodes.find(n => n.id === 'belief-1') as Node<BasicBeliefNodeData>;
        const unchangedBelief2 = state.nodes.find(n => n.id === 'belief-2') as Node<BasicBeliefNodeData>;

        expect(updatedBelief1.data.belief.value).toBe('goodbye');
        expect(unchangedBelief2.data.belief.value).toBe('chair');
        expect(unchangedBelief2.data.belief.type).toBe('object');
      });
    });

    it('should handle multiple rapid updates to belief value', async () => {
      const mockNode: Node<BasicBeliefNodeData> = {
        id: 'belief-1',
        type: 'basic_belief',
        position: { x: 0, y: 0 },
        data: {
          label: 'Belief',
          droppable: true,
          belief: { type: 'semantic', id: 'test', value: 'test value', description: "test description", label: 'Detected with LLM:' },
          hasReduce: true,
        },
      };

      useFlowStore.setState({
        nodes: [mockNode],
        edges: [],
      });

      renderWithProviders(
        <BasicBeliefNode
          id={mockNode.id}
          type={mockNode.type as string}
          data={mockNode.data as any}
          selected={false}
          isConnectable={true}
          zIndex={0}
          dragging={false}
          selectable={true}
          deletable={true}
          draggable={true}
          positionAbsoluteX={0}
          positionAbsoluteY={0}
        />
      );

      const input = screen.getByDisplayValue('test value') as HTMLInputElement;
      
      await user.type(input, '1');
      await waitFor(() => {
        const state = useFlowStore.getState();
        const nodeData = state.nodes[0].data as BasicBeliefNodeData;
        expect(nodeData.belief.value).toBe('test value');
      });

      await user.type(input, '2');
      await waitFor(() => {
        const state = useFlowStore.getState();
        const nodeData = state.nodes[0].data as BasicBeliefNodeData;
        expect(nodeData.belief.value).toBe('test value');
      });

      await user.type(input, '{enter}');
      await waitFor(() => {
        const state = useFlowStore.getState();
        const nodeData = state.nodes[0].data as BasicBeliefNodeData;
        expect(nodeData.belief.value).toBe('test value12');
      });
    });
  });
});