import { describe, it, expect, beforeEach } from 'vitest';
import { VariableResolver } from '../VariableResolver';
import { ValidationError } from '../errors';
import { Workflow, IterationContext } from '../types';

describe('VariableResolver', () => {
  let workflow: Workflow;
  let resolver: VariableResolver;

  beforeEach(() => {
    workflow = {
      variables: {
        globalVar: 'global-value',
        config: { key: 'value' }
      },
      objectsForIteration: {
        services: {
          auth: { code: 'auth.ts' },
          user: { code: 'user.ts' }
        },
        set1: {
          auth: { output: 'auth-result' },
          user: { output: 'user-result' }
        },
        subSet: {
          config: { output: 'config-result' },
          data: { output: 'data-result' }
        }
      }
    };
    resolver = new VariableResolver(workflow);
  });

  describe('resolve', () => {
    it('should resolve global variables', () => {
      expect(resolver.resolve('globalVar')).toBe('global-value');
      expect(resolver.resolve('config')).toEqual({ key: 'value' });
    });

    it('should throw on invalid global variables', () => {
      expect(() => {
        resolver.resolve('invalidVar');
      }).toThrow(ValidationError);
    });

    it('should resolve output paths', () => {
      resolver.registerOutput('phase1.set1.output', 'result');
      expect(resolver.resolve('$phase1.set1.output')).toBe('result');
    });

    it('should throw on invalid output paths', () => {
      expect(() => {
        resolver.resolve('$invalid.path');
      }).toThrow(ValidationError);
    });

    it('should resolve humanInputRequired paths', () => {
      resolver.registerOutput('phase1.services[auth]', { code: 'auth-code' }, {
        currentIteration: 'auth',
        parentIterations: ['services']
      });

      expect(resolver.resolve('$phase1.services[auth]')).toEqual({
        code: 'auth-code'
      });
    });

    it('should resolve humanInputRequired paths with $this', () => {
      resolver.registerOutput('phase1.services[$this]', { code: 'auth-code' }, {
        currentIteration: 'auth',
        parentIterations: ['services']
      });

      expect(
        resolver.resolve('$phase1.services[$this]', {
          currentIteration: 'auth',
          parentIterations: ['services']
        })
      ).toEqual({
        code: 'auth-code'
      });
    });

    it('should resolve humanInputRequired paths with .all', () => {
      resolver.registerOutput('phase1.services[auth]', { code: 'auth-code' }, {
        currentIteration: 'auth',
        parentIterations: ['services']
      });
      resolver.registerOutput('phase1.services[user]', { code: 'user-code' }, {
        currentIteration: 'user',
        parentIterations: ['services']
      });

      expect(resolver.resolve('$phase1.services.all')).toEqual([
        { code: 'auth-code' },
        { code: 'user-code' }
      ]);
    });
  });

  describe('registerOutput', () => {
    const context: IterationContext = {
      currentIteration: 'auth',
      parentIterations: ['services']
    };

    it('should register simple outputs', () => {
      resolver.registerOutput('phase1.set1.output', 'result');
      expect(resolver.getPhaseOutputs('phase1')).toEqual({
        set1: { output: 'result' }
      });
    });

    it('should register nested outputs', () => {
      resolver.registerOutput('phase1.set1[auth].output', 'auth-result', context);
      resolver.registerOutput('phase1.set1[user].output', 'user-result', context);

      expect(resolver.getPhaseOutputs('phase1')).toEqual({
        set1: {
          auth: { output: 'auth-result' },
          user: { output: 'user-result' }
        }
      });
    });

    it('should handle $this in output paths', () => {
      resolver.registerOutput('phase1.set1[$this].output', 'auth-result', context);
      expect(resolver.getPhaseOutputs('phase1')).toEqual({
        set1: {
          auth: { output: 'auth-result' }
        }
      });
    });

    it('should create intermediate objects as needed', () => {
      resolver.registerOutput('phase1.set1[auth].nested.deep.output', 'result', context);
      expect(resolver.getPhaseOutputs('phase1')).toEqual({
        set1: {
          auth: {
            nested: {
              deep: {
                output: 'result'
              }
            }
          }
        }
      });
    });
  });

  describe('getPhaseOutputs', () => {
    it('should return undefined for unknown phases', () => {
      expect(resolver.getPhaseOutputs('unknown')).toBeUndefined();
    });

    it('should return all outputs for a phase', () => {
      resolver.registerOutput('phase1.set1.output1', 'result1');
      resolver.registerOutput('phase1.set2.output2', 'result2');

      expect(resolver.getPhaseOutputs('phase1')).toEqual({
        set1: { output1: 'result1' },
        set2: { output2: 'result2' }
      });
    });
  });

  describe('clearOutputs', () => {
    it('should clear all registered outputs', () => {
      resolver.registerOutput('phase1.set1.output', 'result');
      resolver.clearOutputs();
      expect(resolver.getPhaseOutputs('phase1')).toBeUndefined();
    });
  });

  describe('complex scenarios', () => {
    const context: IterationContext = {
      currentIteration: 'auth',
      parentIterations: ['services']
    };

    it('should handle deeply nested iterations', () => {
      // Register outputs for nested iterations
      resolver.registerOutput(
        'phase1.set1[$this].subSet[config].output',
        'auth-config-result',
        context
      );

      // Try resolving with different contexts
      expect(
        resolver.resolve('$phase1.set1[auth].subSet[config].output', context)
      ).toBe('auth-config-result');
    });

    it('should collect all outputs correctly', () => {
      // Register multiple outputs
      resolver.registerOutput('phase1.set1[auth].output', 'auth-result', context);
      resolver.registerOutput('phase1.set1[user].output', 'user-result', context);

      // Collect all outputs
      const allResults = resolver.resolve('$phase1.set1.all', context);
      expect(allResults).toEqual([
        { output: 'auth-result' },
        { output: 'user-result' }
      ]);
    });

    it('should validate against objectsForIteration consistently', () => {
      // Should work with valid service
      resolver.registerOutput('phase1.services[auth].output', 'result', context);

      // Should fail with invalid service
      expect(() => {
        resolver.registerOutput(
          'phase1.services[invalid].output',
          'result',
          context
        );
      }).toThrow(ValidationError);
    });
  });
}); 