/**
 * Tests for DAG
 */

import { DAG } from './dag';
import { Step } from '../types/workflow';

describe('DAG', () => {
  describe('build and topological sort', () => {
    it('should build a simple linear DAG', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2', depends_on: ['step1'] },
        { id: 'step3', action: 'action3', depends_on: ['step2'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      const sorted = dag.topologicalSort();
      expect(sorted.map(s => s.id)).toEqual(['step1', 'step2', 'step3']);
    });

    it('should handle parallel steps', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2', depends_on: ['step1'] },
        { id: 'step3', action: 'action3', depends_on: ['step1'] },
        { id: 'step4', action: 'action4', depends_on: ['step2', 'step3'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      const sorted = dag.topologicalSort();
      expect(sorted[0].id).toBe('step1');
      expect(sorted[3].id).toBe('step4');
      // step2 and step3 can be in any order
      expect(sorted.slice(1, 3).map(s => s.id).sort()).toEqual(['step2', 'step3']);
    });

    it('should detect circular dependencies', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1', depends_on: ['step2'] },
        { id: 'step2', action: 'action2', depends_on: ['step1'] },
      ];

      const dag = new DAG();
      expect(() => dag.build(steps)).toThrow('Circular dependency detected');
    });

    it('should detect missing dependencies', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1', depends_on: ['nonexistent'] },
      ];

      const dag = new DAG();
      expect(() => dag.build(steps)).toThrow('depends on unknown step');
    });

    it('should detect duplicate step IDs', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step1', action: 'action2' },
      ];

      const dag = new DAG();
      expect(() => dag.build(steps)).toThrow('Duplicate step ID');
    });
  });

  describe('getExecutionLevels', () => {
    it('should group steps that can run in parallel', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2', depends_on: ['step1'] },
        { id: 'step3', action: 'action3', depends_on: ['step1'] },
        { id: 'step4', action: 'action4', depends_on: ['step2', 'step3'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      const levels = dag.getExecutionLevels();

      expect(levels.length).toBe(3);
      expect(levels[0].map(s => s.id)).toEqual(['step1']);
      expect(levels[1].map(s => s.id).sort()).toEqual(['step2', 'step3']);
      expect(levels[2].map(s => s.id)).toEqual(['step4']);
    });

    it('should handle independent parallel branches', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2' },
        { id: 'step3', action: 'action3', depends_on: ['step1'] },
        { id: 'step4', action: 'action4', depends_on: ['step2'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      const levels = dag.getExecutionLevels();

      expect(levels.length).toBe(2);
      expect(levels[0].map(s => s.id).sort()).toEqual(['step1', 'step2']);
      expect(levels[1].map(s => s.id).sort()).toEqual(['step3', 'step4']);
    });
  });

  describe('getEntryPoints', () => {
    it('should return steps with no dependencies', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2' },
        { id: 'step3', action: 'action3', depends_on: ['step1'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      const entryPoints = dag.getEntryPoints();
      expect(entryPoints.map(s => s.id).sort()).toEqual(['step1', 'step2']);
    });
  });

  describe('getExitPoints', () => {
    it('should return steps with no dependents', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2', depends_on: ['step1'] },
        { id: 'step3', action: 'action3', depends_on: ['step1'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      const exitPoints = dag.getExitPoints();
      expect(exitPoints.map(s => s.id).sort()).toEqual(['step2', 'step3']);
    });
  });

  describe('getDependencies and getDependents', () => {
    it('should return correct dependencies and dependents', () => {
      const steps: Step[] = [
        { id: 'step1', action: 'action1' },
        { id: 'step2', action: 'action2', depends_on: ['step1'] },
        { id: 'step3', action: 'action3', depends_on: ['step1'] },
        { id: 'step4', action: 'action4', depends_on: ['step2', 'step3'] },
      ];

      const dag = new DAG();
      dag.build(steps);

      // Check dependencies
      expect(dag.getDependencies('step1').map(s => s.id)).toEqual([]);
      expect(dag.getDependencies('step2').map(s => s.id)).toEqual(['step1']);
      expect(dag.getDependencies('step4').map(s => s.id).sort()).toEqual(['step2', 'step3']);

      // Check dependents
      expect(dag.getDependents('step1').map(s => s.id).sort()).toEqual(['step2', 'step3']);
      expect(dag.getDependents('step2').map(s => s.id)).toEqual(['step4']);
      expect(dag.getDependents('step4').map(s => s.id)).toEqual([]);
    });
  });
});
