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 NormNode, {
  NormReduce,
  type NormNodeData,
  NormConnectionSource, NormConnectionTarget
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode';
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
import type { Node } from '@xyflow/react';
import '@testing-library/jest-dom'



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

  beforeEach(() => {
    user = userEvent.setup();
  });

  describe('Rendering', () => {
    it('should render the norm node with default data', () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

      renderWithProviders(
        <NormNode
          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.getByPlaceholderText('Pepper should ...')).toBeInTheDocument();
    });

    it('should render with pre-populated norm text', () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'Be respectful to humans',
          hasReduce: true,
        },
      };

      renderWithProviders(
        <NormNode
          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('Be respectful to humans');
      expect(input).toBeInTheDocument();
    });

    it('should render with selected state', () => {
      const mockNode: Node<NormNodeData> = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
          critical: false
        },
      };

      renderWithProviders( 
        <NormNode
          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 norm = screen.getByText("Norm :")
      expect(norm).toBeInTheDocument();
    });

    it('should render with dragging state', () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'Dragged norm',
          hasReduce: true,
        },
      };

      renderWithProviders(
        <NormNode
          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('Dragged norm');
      expect(input).toBeInTheDocument();
    });
  });

  describe('User Interactions', () => {
    it('should update norm text when user types in the input field', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Pepper should ...');
      await user.type(input, 'Be polite to guests{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'norm-1');
        expect(updatedNode?.data.norm).toBe('Be polite to guests');
      });
    });

    it('should handle clearing the norm text', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'Initial norm text',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Initial norm text') as HTMLInputElement;

      // clearing the norm text is the same as just deleting all characters one by one
      // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/
      for (let a = 0; a < 'Initial norm text'.length; a++){
        await user.type(input, '{backspace}')
      }
      await user.type(input,'{enter}')

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNode = state.nodes.find(n => n.id === 'norm-1');
        expect(updatedNode?.data.norm).toBe('');
      });
    });

    it('should update norm text multiple times', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Pepper should ...');
      await user.type(input, 'First norm{enter}');
      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe('First norm');
      });


      // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/
      for (let a = 0; a < 'First norm'.length; a++){
        await user.type(input, '{backspace}')
      }

      await user.type(input, 'Second norm{enter}');
      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe('Second norm');
      });
    });

    it('should handle special characters in norm text', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Pepper should ...');
      await user.type(input, "Don't harm & be nice!{enter}" );

      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe("Don't harm & be nice!");
      });
    });

    it('should handle long norm text', async () => {
      const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Pepper should ...');
      await user.type(input, longText);
      await user.type(input, "{enter}")

      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe(longText);
      });
    });
  });

  describe('NormReduce Function', () => {
    it('should reduce a norm node to its essential data', () => {
      const normNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Safety Norm',
          droppable: true,
          norm: 'Never harm humans',
          hasReduce: true,
        },
      };

      const allNodes: Node[] = [normNode];
      const result = NormReduce(normNode, allNodes);

      expect(result).toEqual({
        id: 'norm-1',
        label: 'Safety Norm',
        norm: 'Never harm humans',
      });
    });

    it('should reduce multiple norm nodes independently', () => {
      const norm1: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Norm 1',
          droppable: true,
          norm: 'Be helpful',
          hasReduce: true,
        },
      };

      const norm2: Node = {
        id: 'norm-2',
        type: 'norm',
        position: { x: 100, y: 0 },
        data: {
          label: 'Norm 2',
          droppable: true,
          norm: 'Be honest',
          hasReduce: true,
        },
      };

      const allNodes: Node[] = [norm1, norm2];

      const result1 = NormReduce(norm1, allNodes);
      const result2 = NormReduce(norm2, allNodes);

      expect(result1.id).toBe('norm-1');
      expect(result1.norm).toBe('Be helpful');
      expect(result2.id).toBe('norm-2');
      expect(result2.norm).toBe('Be honest');
    });

    it('should handle empty norm text', () => {
      const normNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Empty Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

      const result = NormReduce(normNode, [normNode]);

      expect(result.norm).toBe('');
      expect(result.id).toBe('norm-1');
    });

    it('should preserve node label in reduction', () => {
      const normNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Custom Label',
          droppable: false,
          norm: 'Test norm',
          hasReduce: false,
        },
      };

      const result = NormReduce(normNode, [normNode]);

      expect(result.label).toBe('Custom Label');
    });
  });

  describe('NormConnects Function', () => {
    it('should handle connection without errors', () => {
      const normNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'Test',
          hasReduce: true,
        },
      };

      const phaseNode: Node = {
        id: 'phase-1',
        type: 'phase',
        position: { x: 100, y: 0 },
        data: {
          label: 'Phase 1',
          droppable: true,
          children: [],
          hasReduce: true,
        },
      };

      expect(() => {
        NormConnectionSource(normNode, phaseNode.id);
      }).not.toThrow();
    });

    it('should handle connection when norm is target', () => {
      const normNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'Test',
          hasReduce: true,
        },
      };

      const phaseNode: Node = {
        id: 'phase-1',
        type: 'phase',
        position: { x: 100, y: 0 },
        data: {
          label: 'Phase 1',
          droppable: true,
          children: [],
          hasReduce: true,
        },
      };

      expect(() => {
        NormConnectionTarget(normNode, phaseNode.id);
      }).not.toThrow();
    });

    it('should handle self-connection', () => {
      const normNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'Test',
          hasReduce: true,
        },
      };

      expect(() => {
        NormConnectionTarget(normNode, normNode.id);
        NormConnectionSource(normNode, normNode.id);
      }).not.toThrow();
    });
  });

  describe('Integration with Store', () => {
    it('should properly update the store when editing norm text', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Pepper should ...');

      // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/
      for (let a = 0; a < 20; a++){
        await user.type(input, '{backspace}')
      }
      await user.type(input, 'New norm value{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        expect(state.nodes).toHaveLength(1);
        expect(state.nodes[0].id).toBe('norm-1');
        expect(state.nodes[0].data.norm).toBe('New norm value');
      });
    });

    it('should properly update the store when editing critical checkbox', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: '',
          hasReduce: true,
          critical: false,
        },
      };

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

      renderWithProviders(
        <NormNode
          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 checkbox = screen.getByLabelText('Critical:');
      await user.click(checkbox);

      await waitFor(() => {
        const state = useFlowStore.getState();
        expect(state.nodes).toHaveLength(1);
        expect(state.nodes[0].id).toBe('norm-1');
        expect(state.nodes[0].data.norm).toBe('');
        expect(state.nodes[0].data.critical).toBe(true);
      });
    });

    it('should not affect other nodes when updating one norm node', async () => {
      const norm1: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Norm 1',
          droppable: true,
          norm: 'Original norm 1',
          hasReduce: true,
        },
      };

      const norm2: Node = {
        id: 'norm-2',
        type: 'norm',
        position: { x: 100, y: 0 },
        data: {
          label: 'Norm 2',
          droppable: true,
          norm: 'Original norm 2',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          id={norm1.id}
          type={norm1.type as string}
          data={norm1.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('Original norm 1') as HTMLInputElement;


      // TODO: FIGURE OUT A MORE EFFICIENT WAY, I"M SORRY, user.clear() just doesn't work:/
      for (let a = 0; a < 20; a++){
        await user.type(input, '{backspace}')
      }
      await user.type(input, 'Updated norm 1{enter}');

      await waitFor(() => {
        const state = useFlowStore.getState();
        const updatedNorm1 = state.nodes.find(n => n.id === 'norm-1');
        const unchangedNorm2 = state.nodes.find(n => n.id === 'norm-2');

        expect(updatedNorm1?.data.norm).toBe('Updated norm 1');
        expect(unchangedNorm2?.data.norm).toBe('Original norm 2');
      });
    });

    it('should maintain data consistency with multiple rapid updates', async () => {
      const mockNode: Node = {
        id: 'norm-1',
        type: 'norm',
        position: { x: 0, y: 0 },
        data: {
          label: 'Test Norm',
          droppable: true,
          norm: 'haa haa fuyaaah - link',
          hasReduce: true,
        },
      };

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

      renderWithProviders(
        <NormNode
          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('Pepper should ...');

      await user.type(input, 'a');
      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link');
      });

      await user.type(input, 'b');
      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link');
      });

      await user.type(input, 'c');
      await waitFor(() => {
        expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link');
      }, { timeout: 3000 });
    });
  });
});