import { validateDslGraph } from './dsl-validate.util';

describe('validateDslGraph', () => {
  const validDsl = {
    nodes: [
      { id: 't1', type: 'manual_trigger' },
      { id: 'n1', type: 'http_request' },
      { id: 'n2', type: 'send_notification' },
    ],
    edges: [
      { source: 't1', target: 'n1' },
      { source: 'n1', target: 'n2' },
    ],
  };

  describe('valid input', () => {
    it('accepts a simple linear DAG', () => {
      expect(() => validateDslGraph(validDsl)).not.toThrow();
    });

    it('accepts a diamond DAG', () => {
      expect(() =>
        validateDslGraph({
          nodes: [
            { id: 't1', type: 'manual_trigger' },
            { id: 'a', type: 'http_request' },
            { id: 'b', type: 'http_request' },
            { id: 'c', type: 'send_notification' },
          ],
          edges: [
            { source: 't1', target: 'a' },
            { source: 't1', target: 'b' },
            { source: 'a', target: 'c' },
            { source: 'b', target: 'c' },
          ],
        }),
      ).not.toThrow();
    });

    it('accepts a trigger with no successors', () => {
      expect(() =>
        validateDslGraph({
          nodes: [{ id: 't1', type: 'cron_trigger' }],
          edges: [],
        }),
      ).not.toThrow();
    });
  });

  describe('shape errors', () => {
    it('rejects null', () => {
      expect(() => validateDslGraph(null)).toThrow('DSL must be an object');
    });

    it('rejects missing nodes array', () => {
      expect(() => validateDslGraph({ edges: [] })).toThrow(
        'DSL.nodes must be an array',
      );
    });

    it('rejects missing edges array', () => {
      expect(() =>
        validateDslGraph({ nodes: [{ id: 't1', type: 'manual_trigger' }] }),
      ).toThrow('DSL.edges must be an array');
    });

    it('rejects empty nodes', () => {
      expect(() => validateDslGraph({ nodes: [], edges: [] })).toThrow(
        'at least one node',
      );
    });

    it('rejects node without id', () => {
      expect(() =>
        validateDslGraph({
          nodes: [{ type: 'manual_trigger' }],
          edges: [],
        }),
      ).toThrow('non-empty string id');
    });

    it('rejects duplicate node ids', () => {
      expect(() =>
        validateDslGraph({
          nodes: [
            { id: 't1', type: 'manual_trigger' },
            { id: 't1', type: 'http_request' },
          ],
          edges: [],
        }),
      ).toThrow('Duplicate node id');
    });

    it('rejects node without type', () => {
      expect(() =>
        validateDslGraph({
          nodes: [{ id: 't1' }],
          edges: [],
        }),
      ).toThrow('non-empty string type');
    });

    it('rejects DSL with no trigger node', () => {
      expect(() =>
        validateDslGraph({
          nodes: [{ id: 'n1', type: 'http_request' }],
          edges: [],
        }),
      ).toThrow('at least one *_trigger node');
    });
  });

  describe('edge errors', () => {
    it('rejects edge with unknown source', () => {
      expect(() =>
        validateDslGraph({
          nodes: [{ id: 't1', type: 'manual_trigger' }],
          edges: [{ source: 'ghost', target: 't1' }],
        }),
      ).toThrow('unknown source');
    });

    it('rejects edge with unknown target', () => {
      expect(() =>
        validateDslGraph({
          nodes: [{ id: 't1', type: 'manual_trigger' }],
          edges: [{ source: 't1', target: 'ghost' }],
        }),
      ).toThrow('unknown target');
    });

    it('rejects self-loop', () => {
      expect(() =>
        validateDslGraph({
          nodes: [
            { id: 't1', type: 'manual_trigger' },
            { id: 'a', type: 'http_request' },
          ],
          edges: [
            { source: 't1', target: 'a' },
            { source: 'a', target: 'a' },
          ],
        }),
      ).toThrow('Self-loop');
    });
  });

  describe('cycle detection', () => {
    it('rejects a 2-node cycle', () => {
      expect(() =>
        validateDslGraph({
          nodes: [
            { id: 't1', type: 'manual_trigger' },
            { id: 'a', type: 'http_request' },
            { id: 'b', type: 'http_request' },
          ],
          edges: [
            { source: 't1', target: 'a' },
            { source: 'a', target: 'b' },
            { source: 'b', target: 'a' },
          ],
        }),
      ).toThrow('cycle');
    });

    it('rejects a 3-node cycle', () => {
      expect(() =>
        validateDslGraph({
          nodes: [
            { id: 't1', type: 'manual_trigger' },
            { id: 'a', type: 'http_request' },
            { id: 'b', type: 'http_request' },
            { id: 'c', type: 'http_request' },
          ],
          edges: [
            { source: 't1', target: 'a' },
            { source: 'a', target: 'b' },
            { source: 'b', target: 'c' },
            { source: 'c', target: 'a' },
          ],
        }),
      ).toThrow('cycle');
    });

    it('error message lists nodes still in the cycle', () => {
      try {
        validateDslGraph({
          nodes: [
            { id: 't1', type: 'manual_trigger' },
            { id: 'a', type: 'http_request' },
            { id: 'b', type: 'http_request' },
          ],
          edges: [
            { source: 't1', target: 'a' },
            { source: 'a', target: 'b' },
            { source: 'b', target: 'a' },
          ],
        });
        fail('expected validation to throw');
      } catch (err: any) {
        expect(err.message).toMatch(/a|b/);
      }
    });
  });
});
