import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TextDirectiveHandler } from './TextDirectiveHandler.js';
import { createMockStateService, createMockValidationService, createMockResolutionService } from '@tests/utils/testFactories.js';
import { DirectiveError, DirectiveErrorCode } from '@services/pipeline/DirectiveService/errors/DirectiveError.js';
import { ResolutionError } from '@services/resolution/ResolutionService/errors/ResolutionError.js';
import { ResolutionErrorCode } from '@services/resolution/ResolutionService/IResolutionService.js';
import type { DirectiveNode } from 'meld-spec';
import type { IStateService } from '@services/state/StateService/IStateService.js';
import { ErrorCollector } from '@tests/utils/ErrorTestUtils.js';
import { ErrorSeverity } from '@core/errors/MeldError.js';

describe('TextDirectiveHandler Integration', () => {
  let handler: TextDirectiveHandler;
  let stateService: ReturnType<typeof createMockStateService>;
  let validationService: ReturnType<typeof createMockValidationService>;
  let resolutionService: ReturnType<typeof createMockResolutionService>;
  let clonedState: IStateService;

  beforeEach(() => {
    clonedState = {
      setTextVar: vi.fn(),
      getTextVar: vi.fn(),
      getDataVar: vi.fn(),
      clone: vi.fn(),
    } as unknown as IStateService;

    stateService = {
      setTextVar: vi.fn(),
      getTextVar: vi.fn(),
      getDataVar: vi.fn(),
      clone: vi.fn().mockReturnValue(clonedState)
    } as unknown as IStateService;

    validationService = createMockValidationService();
    resolutionService = createMockResolutionService();
    handler = new TextDirectiveHandler(validationService, stateService, resolutionService);
  });

  describe('complex scenarios', () => {
    it('should handle nested variable references', async () => {
      const node: DirectiveNode = {
        type: 'Directive',
        directive: {
          kind: 'text',
          identifier: 'greeting',
          value: 'Hello {{user.{{type}}.name}}!'
        }
      };

      const context = {
        state: stateService,
        currentFilePath: 'test.meld'
      };

      vi.mocked(resolutionService.resolveInContext)
        .mockResolvedValue('Hello Alice!');

      const result = await handler.execute(node, context);
      expect(clonedState.setTextVar).toHaveBeenCalledWith('greeting', 'Hello Alice!');
    });

    it('should handle mixed string literals and variables', async () => {
      const node: DirectiveNode = {
        type: 'Directive',
        directive: {
          kind: 'text',
          identifier: 'message',
          value: '{{prefix}} "quoted {{name}}" {{suffix}}'
        }
      };

      const context = {
        state: stateService,
        currentFilePath: 'test.meld'
      };

      vi.mocked(resolutionService.resolveInContext)
        .mockResolvedValue('Hello "quoted World" !');

      const result = await handler.execute(node, context);
      expect(clonedState.setTextVar).toHaveBeenCalledWith('message', 'Hello "quoted World" !');
    });

    it('should handle complex data structure access', async () => {
      const node: DirectiveNode = {
        type: 'Directive',
        directive: {
          kind: 'text',
          identifier: 'userInfo',
          value: '{{user.contacts[{{index}}].email}}'
        }
      };

      const context = {
        state: stateService,
        currentFilePath: 'test.meld'
      };

      vi.mocked(resolutionService.resolveInContext)
        .mockResolvedValue('second@example.com');

      const result = await handler.execute(node, context);
      expect(clonedState.setTextVar).toHaveBeenCalledWith('userInfo', 'second@example.com');
    });

    it('should handle environment variables with fallbacks', async () => {
      const node: DirectiveNode = {
        type: 'Directive',
        directive: {
          kind: 'text',
          identifier: 'config',
          value: '{{ENV_HOST:-localhost}}:{{ENV_PORT:-3000}}'
        }
      };

      const context = {
        state: stateService,
        currentFilePath: 'test.meld'
      };

      process.env.ENV_HOST = 'example.com';
      // ENV_PORT not set, should use fallback

      vi.mocked(resolutionService.resolveInContext)
        .mockResolvedValue('example.com:3000');

      const result = await handler.execute(node, context);
      expect(clonedState.setTextVar).toHaveBeenCalledWith('config', 'example.com:3000');

      delete process.env.ENV_HOST;
    });

    it.todo('should handle circular reference detection - Complex error handling deferred for V1');

    it.todo('should handle error propagation through the stack - Complex error propagation deferred for V1');

    it('should handle validation errors with proper context', async () => {
      const node: DirectiveNode = {
        type: 'Directive',
        directive: {
          kind: 'text',
          identifier: 'invalid',
          value: null // Invalid value - should be a string
        },
        location: {
          start: { line: 5, column: 1 },
          end: { line: 5, column: 25 }
        }
      };

      const context = {
        state: stateService,
        currentFilePath: 'test.meld'
      };

      // Mock validation service to throw a DirectiveError
      validationService.validate = vi.fn().mockImplementation(() => {
        // Create an error with location and other properties set correctly
        const error = new DirectiveError(
          'Invalid text directive value',
          'text',
          DirectiveErrorCode.VALIDATION_FAILED,
          {
            node,
            context: {
              ...context,
              filePath: 'test.meld'
            },
            location: {
              ...node.location,
              start: {
                ...node.location.start,
                line: 5,
                column: 1
              }
            }
          }
        );
        
        // Set the severity property directly on the error
        // @ts-ignore - We know this will work
        error.severity = ErrorSeverity.Fatal;
        
        throw error;
      });

      // Use ErrorCollector to test both strict and permissive modes
      const errorCollector = new ErrorCollector();
      
      // Test strict mode (should throw)
      await expect(async () => {
        try {
          await handler.execute(node, context);
        } catch (error) {
          // Ensure we're passing an appropriate error to the handler
          if (error instanceof DirectiveError) {
            errorCollector.handleError(error);
          }
          throw error;
        }
      }).rejects.toThrow(DirectiveError);
      
      // Verify error was collected with correct severity
      expect(errorCollector.getAllErrors()).toHaveLength(1);
      expect(errorCollector.getAllErrors()[0].severity).toBe(ErrorSeverity.Fatal);
      expect(errorCollector.getAllErrors()[0].message).toContain('Invalid text directive value');
      
      // Verify error contains location information
      const error = errorCollector.getAllErrors()[0];
      expect(error.context).toBeDefined();
      
      // With the refactored code, the location is now in a different structure
      // The DirectiveError wraps the location in the context.node.location
      expect(error.context.node).toBeDefined();
      expect(error.context.node.location).toBeDefined();
      expect(error.context.node.location.start.line).toBe(5);
      expect(error.context.context).toBeDefined();
      expect(error.context.context.currentFilePath).toBe('test.meld');
    });

    it.todo('should handle mixed directive types - Complex directive interaction deferred for V1');
  });
}); 