// 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, expect, jest, beforeEach } from '@jest/globals';
import {type Connection, getOutgoers, type Node} from '@xyflow/react';
import {ruleResult} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/HandleRuleLogic.ts";
import {BasicBeliefReduce} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx";
import {
  BeliefGlobalReduce, noBeliefCycles,
  noMatchingLeftRightBelief
} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BeliefGlobals.ts";
import { InferredBeliefReduce } from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx";
import useFlowStore from "../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx";
import * as BasicModule from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode';
import * as InferredModule from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx';
import * as FlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';


describe('BeliefGlobalReduce', () => {
  const nodes: Node[] = [];

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('delegates to BasicBeliefReduce for basic_belief nodes', () => {
    const spy = jest
      .spyOn(BasicModule, 'BasicBeliefReduce')
      .mockReturnValue('basic-result' as any);

    const node = { id: '1', type: 'basic_belief' } as Node;

    const result = BeliefGlobalReduce(node, nodes);

    expect(spy).toHaveBeenCalledWith(node, nodes);
    expect(result).toBe('basic-result');
  });

  it('delegates to InferredBeliefReduce for inferred_belief nodes', () => {
    const spy = jest
      .spyOn(InferredModule, 'InferredBeliefReduce')
      .mockReturnValue('inferred-result' as any);

    const node = { id: '2', type: 'inferred_belief' } as Node;

    const result = BeliefGlobalReduce(node, nodes);

    expect(spy).toHaveBeenCalledWith(node, nodes);
    expect(result).toBe('inferred-result');
  });

  it('returns undefined for unknown node types', () => {
    const node = { id: '3', type: 'other' } as Node;

    const result = BeliefGlobalReduce(node, nodes);

    expect(result).toBeUndefined();
    expect(BasicBeliefReduce).not.toHaveBeenCalled();
    expect(InferredBeliefReduce).not.toHaveBeenCalled();
  });
});

describe('noMatchingLeftRightBelief rule', () => {
  let getStateSpy: ReturnType<typeof jest.spyOn>;

  beforeEach(() => {
    jest.clearAllMocks();
    getStateSpy = jest.spyOn(FlowStore.default, 'getState');
  });

  it('is satisfied when target node is not an inferred belief', () => {
    getStateSpy.mockReturnValue({
      nodes: [{ id: 't1', type: 'basic_belief' }],
    } as any);

    const result = noMatchingLeftRightBelief(
      { source: 's1', target: 't1' } as Connection,
      null as any
    );

    expect(result).toBe(ruleResult.satisfied);
  });

  it('is satisfied when inferred belief has no matching left/right', () => {
    getStateSpy.mockReturnValue({
      nodes: [
        {
          id: 't1',
          type: 'inferred_belief',
          data: {
            inferredBelief: {
              left: 'a',
              right: 'b',
            },
          },
        },
      ],
    } as any);

    const result = noMatchingLeftRightBelief(
      { source: 'c', target: 't1' } as Connection,
      null as any
    );

    expect(result).toBe(ruleResult.satisfied);
  });

  it('is NOT satisfied when source matches left input', () => {
    getStateSpy.mockReturnValue({
      nodes: [
        {
          id: 't1',
          type: 'inferred_belief',
          data: {
            inferredBelief: {
              left: 's1',
              right: 's2',
            },
          },
        },
      ],
    } as any);

    const result = noMatchingLeftRightBelief(
      { source: 's1', target: 't1' } as Connection,
      null as any
    );

    expect(result.isSatisfied).toBe(false);
    if (!(result.isSatisfied)) {
      expect(result.message).toContain(
        'Connecting one belief to both input handles of an inferred belief node is not allowed'
      );
    }
  });

  it('is NOT satisfied when source matches right input', () => {
    getStateSpy.mockReturnValue({
      nodes: [
        {
          id: 't1',
          type: 'inferred_belief',
          data: {
            inferredBelief: {
              left: 's1',
              right: 's2',
            },
          },
        },
      ],
    } as any);

    const result = noMatchingLeftRightBelief(
      { source: 's2', target: 't1' } as Connection,
      null as any
    );

    expect(result.isSatisfied).toBe(false);
    if (!(result.isSatisfied)) {
      expect(result.message).toContain(
        'Connecting one belief to both input handles of an inferred belief node is not allowed'
      );
    }
  });
});


jest.mock('@xyflow/react', () => ({
  getOutgoers: jest.fn(),
  getConnectedEdges: jest.fn(), // include if some tests require it
}));

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

  it('returns notSatisfied when source === target', () => {
    const result = noBeliefCycles({ source: 'n1', target: 'n1' } as any, null as any);
    expect(result.isSatisfied).toBe(false);
    if (!(result.isSatisfied)) {
      expect(result.message).toContain('Cyclical connection exists');
    }
  });

  it('returns satisfied when there are no outgoing inferred beliefs', () => {
    jest.spyOn(useFlowStore, 'getState').mockReturnValue({
      nodes: [{ id: 'n1', type: 'inferred_belief' }],
      edges: [],
    } as any);

    (getOutgoers as jest.Mock).mockReturnValue([]);

    const result = noBeliefCycles({ source: 'n1', target: 'n2' } as any, null as any);
    expect(result).toBe(ruleResult.satisfied);
  });

  it('returns notSatisfied for direct cycle', () => {
    jest.spyOn(useFlowStore, 'getState').mockReturnValue({
      nodes: [
        { id: 'n1', type: 'inferred_belief' },
        { id: 'n2', type: 'inferred_belief' },
      ],
      edges: [{ source: 'n2', target: 'n1' }],
    } as any);

    // @ts-expect-error is acting up
    (getOutgoers as jest.Mock).mockImplementation(({ id }) => {
      if (id === 'n2') return [{ id: 'n1', type: 'inferred_belief' }];
      return [];
    });

    const result = noBeliefCycles({ source: 'n1', target: 'n2' } as any, null as any);
    expect(result.isSatisfied).toBe(false);
    if (!(result.isSatisfied)) {
      expect(result.message).toContain('Cyclical connection exists');
    }
  });

  it('returns notSatisfied for indirect cycle', () => {
    jest.spyOn(useFlowStore, 'getState').mockReturnValue({
      nodes: [
        { id: 'A', type: 'inferred_belief' },
        { id: 'B', type: 'inferred_belief' },
        { id: 'C', type: 'inferred_belief' },
      ],
      edges: [
        { source: 'A', target: 'B' },
        { source: 'B', target: 'C' },
        { source: 'C', target: 'A' },
      ],
    } as any);

    // @ts-expect-error is acting up
    (getOutgoers as jest.Mock).mockImplementation(({ id }) => {
      const mapping: Record<string, any[]> = {
        A: [{ id: 'B', type: 'inferred_belief' }],
        B: [{ id: 'C', type: 'inferred_belief' }],
        C: [{ id: 'A', type: 'inferred_belief' }],
      };
      return mapping[id] || [];
    });

    const result = noBeliefCycles({ source: 'A', target: 'B' } as any, null as any);
    expect(result.isSatisfied).toBe(false);
    if (!(result.isSatisfied)) {
      expect(result.message).toContain('Cyclical connection exists');
    }
  });

  it('returns satisfied when no cycle exists in a multi-node graph', () => {
    jest.spyOn(useFlowStore, 'getState').mockReturnValue({
      nodes: [
        { id: 'A', type: 'inferred_belief' },
        { id: 'B', type: 'inferred_belief' },
        { id: 'C', type: 'inferred_belief' },
      ],
      edges: [
        { source: 'A', target: 'B' },
        { source: 'B', target: 'C' },
      ],
    } as any);

    // @ts-expect-error is acting up
    (getOutgoers as jest.Mock).mockImplementation(({ id }) => {
      const mapping: Record<string, any[]> = {
        A: [{ id: 'B', type: 'inferred_belief' }],
        B: [{ id: 'C', type: 'inferred_belief' }],
        C: [],
      };
      return mapping[id] || [];
    });

    const result = noBeliefCycles({ source: 'A', target: 'B' } as any, null as any);
    expect(result).toBe(ruleResult.satisfied);
  });
});