// 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 { 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 GoalNode, { GoalReduce, type GoalNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode';
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
import type { Node } from '@xyflow/react';
import '@testing-library/jest-dom';
import { GoalNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.default.ts';
import { defaultPlan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor/Plan.default.ts';

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

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

  it('renders the Goal node with default data', () => {
    const mockNode: Node = {
      id: 'goal-1',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)) },
    };

    renderWithProviders(
      <GoalNode
        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('To ...')).toBeInTheDocument();
  });

  it('updates goal name when user types and commits', async () => {
    const mockNode: Node<GoalNodeData> = {
      id: 'goal-2',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), name: '' },
    };

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

    renderWithProviders(
      <GoalNode
        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('To ...');

    await user.type(input, 'Save the world{enter}');

    await waitFor(() => {
      const state = useFlowStore.getState();
      const updated = state.nodes.find(n => n.id === 'goal-2');
      expect(updated?.data.name).toBe('Save the world');
    });
  });

  it('shows plan message and disabled checked checkbox when plan does not iterate', () => {
    const mockNode: Node<GoalNodeData> = {
      id: 'goal-3',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), plan: defaultPlan, name: 'G' },
    };

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

    renderWithProviders(
      <GoalNode
        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(/Will follow plan 'Default Plan' until all steps complete./i)).toBeInTheDocument();

    const checkbox = screen.getByLabelText(/This plan always succeeds!/i) as HTMLInputElement;
    expect(checkbox).toBeDisabled();
    expect(checkbox.checked).toBe(true);
  });

  it('allows toggling can_fail when plan iterates', async () => {
    // plan with an llm-step will make DoesPlanIterate return true
    const iterPlan = { ...defaultPlan, id: 'p-iter', steps: [{ id: 'a-1', type: 'llm', goal: 'do' }] } as any;
    const mockNode: Node<GoalNodeData> = {
      id: 'goal-4',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), plan: iterPlan, name: 'Iterating' },
    };

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

    renderWithProviders(
      <GoalNode
        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(/Check if this plan fails/i) as HTMLInputElement;
    expect(checkbox).not.toBeDisabled();
    expect(checkbox.checked).toBe(false);

    await user.click(checkbox);

    await waitFor(() => {
      const state = useFlowStore.getState();
      const updated = state.nodes.find(n => n.id === 'goal-4');
      expect(updated?.data.can_fail).toBe(true);
    });
  });

  it('disables the checkbox and shows description when plan includes a checking sub-goal', () => {
    const childGoal: Node = {
      id: 'child-1',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), can_fail: true },
    };

    const p = { ...defaultPlan, id: 'p-2', steps: [{ id: 'child-1', type: 'goal' } as any] } as any;

    const mockNode: Node<GoalNodeData> = {
      id: 'goal-5',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), plan: p, name: 'HasCheck' },
    };

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

    renderWithProviders(
      <GoalNode
        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.getByRole('checkbox') as HTMLInputElement;
    expect(checkbox).toBeDisabled();
    expect(checkbox.checked).toBe(true);

    // description box should be visible because there's a checking subgoal
    expect(screen.getByPlaceholderText('Describe the condition of this goal...')).toBeInTheDocument();
  });

  it('reduces its data correctly (GoalReduce)', () => {
    const childGoal: Node = {
      id: 'child-2',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), can_fail: true },
    };

    const p = { ...defaultPlan, id: 'p-3', steps: [{ id: 'child-2', type: 'goal' } as any] } as any;

    const mockNode: Node = {
      id: 'goal-6',
      type: 'goal',
      position: { x: 0, y: 0 },
      data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), plan: p, name: 'ReduceMe', description: 'desc', can_fail: false },
    };

    const reduced = GoalReduce(mockNode, [mockNode, childGoal]);
    expect(reduced).toEqual({
      id: 'goal-6',
      name: 'ReduceMe',
      description: 'desc',
      can_fail: true,
      plan: {
        id: expect.anything(),
        steps: expect.any(Array),
      }
    });
  });

  it('adds a goal into a plan when a goal is connected to another', () => {
    const source: Node = { id: 'g-src', type: 'goal', position: { x: 0, y: 0 }, data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), name: 'Source' } };
    const target: Node = { id: 'g-target', type: 'goal', position: { x: 0, y: 0 }, data: { ...JSON.parse(JSON.stringify(GoalNodeDefaults)), name: 'Target' } };

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

    // Simulate react-flow connect
    useFlowStore.getState().onConnect({ source: 'g-src', target: 'g-target', sourceHandle: null, targetHandle: null });

    const state = useFlowStore.getState();
    const updatedTarget = state.nodes.find(n => n.id === 'g-target');

    expect(updatedTarget?.data.plan).toBeDefined();
    const plan = updatedTarget?.data.plan as any;
    expect(plan.steps.length).toBe(1);
    expect(plan.steps[0].id).toBe('g-src');
  });
});