import { describe, it, expect, beforeEach, vi } from 'vitest';
import { NodeRepository } from '@/database/node-repository';
import { DatabaseAdapter } from '@/database/database-adapter';
import { ParsedNode } from '@/parsers/node-parser';

describe('NodeRepository - Outputs Handling', () => {
  let repository: NodeRepository;
  let mockDb: DatabaseAdapter;
  let mockStatement: any;

  beforeEach(() => {
    mockStatement = {
      run: vi.fn(),
      get: vi.fn(),
      all: vi.fn()
    };

    mockDb = {
      prepare: vi.fn().mockReturnValue(mockStatement),
      transaction: vi.fn(),
      exec: vi.fn(),
      close: vi.fn(),
      pragma: vi.fn()
    } as any;

    repository = new NodeRepository(mockDb);
  });

  describe('saveNode with outputs', () => {
    it('should save node with outputs and outputNames correctly', () => {
      const outputs = [
        { displayName: 'Done', description: 'Final results when loop completes' },
        { displayName: 'Loop', description: 'Current batch data during iteration' }
      ];
      const outputNames = ['done', 'loop'];

      const node: ParsedNode = {
        style: 'programmatic',
        nodeType: 'nodes-base.splitInBatches',
        displayName: 'Split In Batches',
        description: 'Split data into batches',
        category: 'transform',
        properties: [],
        credentials: [],
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        operations: [],
        version: '3',
        isVersioned: false,
        packageName: 'n8n-nodes-base',
        outputs,
        outputNames
      };

      repository.saveNode(node);

      expect(mockDb.prepare).toHaveBeenCalledWith(`
      INSERT OR REPLACE INTO nodes (
        node_type, package_name, display_name, description,
        category, development_style, is_ai_tool, is_trigger,
        is_webhook, is_versioned, version, documentation,
        properties_schema, operations, credentials_required,
        outputs, output_names
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);

      expect(mockStatement.run).toHaveBeenCalledWith(
        'nodes-base.splitInBatches',
        'n8n-nodes-base',
        'Split In Batches',
        'Split data into batches',
        'transform',
        'programmatic',
        0, // false
        0, // false
        0, // false
        0, // false
        '3',
        null, // documentation
        JSON.stringify([], null, 2), // properties
        JSON.stringify([], null, 2), // operations
        JSON.stringify([], null, 2), // credentials
        JSON.stringify(outputs, null, 2), // outputs
        JSON.stringify(outputNames, null, 2) // output_names
      );
    });

    it('should save node with only outputs (no outputNames)', () => {
      const outputs = [
        { displayName: 'True', description: 'Items that match condition' },
        { displayName: 'False', description: 'Items that do not match condition' }
      ];

      const node: ParsedNode = {
        style: 'programmatic',
        nodeType: 'nodes-base.if',
        displayName: 'IF',
        description: 'Route items based on conditions',
        category: 'transform',
        properties: [],
        credentials: [],
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        operations: [],
        version: '2',
        isVersioned: false,
        packageName: 'n8n-nodes-base',
        outputs
        // no outputNames
      };

      repository.saveNode(node);

      const callArgs = mockStatement.run.mock.calls[0];
      expect(callArgs[15]).toBe(JSON.stringify(outputs, null, 2)); // outputs
      expect(callArgs[16]).toBe(null); // output_names should be null
    });

    it('should save node with only outputNames (no outputs)', () => {
      const outputNames = ['main', 'error'];

      const node: ParsedNode = {
        style: 'programmatic',
        nodeType: 'nodes-base.customNode',
        displayName: 'Custom Node',
        description: 'Custom node with output names only',
        category: 'transform',
        properties: [],
        credentials: [],
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        operations: [],
        version: '1',
        isVersioned: false,
        packageName: 'n8n-nodes-base',
        outputNames
        // no outputs
      };

      repository.saveNode(node);

      const callArgs = mockStatement.run.mock.calls[0];
      expect(callArgs[15]).toBe(null); // outputs should be null
      expect(callArgs[16]).toBe(JSON.stringify(outputNames, null, 2)); // output_names
    });

    it('should save node without outputs or outputNames', () => {
      const node: ParsedNode = {
        style: 'programmatic',
        nodeType: 'nodes-base.httpRequest',
        displayName: 'HTTP Request',
        description: 'Make HTTP requests',
        category: 'input',
        properties: [],
        credentials: [],
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        operations: [],
        version: '4',
        isVersioned: false,
        packageName: 'n8n-nodes-base'
        // no outputs or outputNames
      };

      repository.saveNode(node);

      const callArgs = mockStatement.run.mock.calls[0];
      expect(callArgs[15]).toBe(null); // outputs should be null
      expect(callArgs[16]).toBe(null); // output_names should be null
    });

    it('should handle empty outputs and outputNames arrays', () => {
      const node: ParsedNode = {
        style: 'programmatic',
        nodeType: 'nodes-base.emptyNode',
        displayName: 'Empty Node',
        description: 'Node with empty outputs',
        category: 'misc',
        properties: [],
        credentials: [],
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        operations: [],
        version: '1',
        isVersioned: false,
        packageName: 'n8n-nodes-base',
        outputs: [],
        outputNames: []
      };

      repository.saveNode(node);

      const callArgs = mockStatement.run.mock.calls[0];
      expect(callArgs[15]).toBe(JSON.stringify([], null, 2)); // outputs
      expect(callArgs[16]).toBe(JSON.stringify([], null, 2)); // output_names
    });
  });

  describe('getNode with outputs', () => {
    it('should retrieve node with outputs and outputNames correctly', () => {
      const outputs = [
        { displayName: 'Done', description: 'Final results when loop completes' },
        { displayName: 'Loop', description: 'Current batch data during iteration' }
      ];
      const outputNames = ['done', 'loop'];

      const mockRow = {
        node_type: 'nodes-base.splitInBatches',
        display_name: 'Split In Batches',
        description: 'Split data into batches',
        category: 'transform',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '3',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: JSON.stringify(outputs),
        output_names: JSON.stringify(outputNames)
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.splitInBatches');

      expect(result).toEqual({
        nodeType: 'nodes-base.splitInBatches',
        displayName: 'Split In Batches',
        description: 'Split data into batches',
        category: 'transform',
        developmentStyle: 'programmatic',
        package: 'n8n-nodes-base',
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        isVersioned: false,
        version: '3',
        properties: [],
        operations: [],
        credentials: [],
        hasDocumentation: false,
        outputs,
        outputNames
      });
    });

    it('should retrieve node with only outputs (null outputNames)', () => {
      const outputs = [
        { displayName: 'True', description: 'Items that match condition' }
      ];

      const mockRow = {
        node_type: 'nodes-base.if',
        display_name: 'IF',
        description: 'Route items',
        category: 'transform',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '2',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: JSON.stringify(outputs),
        output_names: null
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.if');

      expect(result.outputs).toEqual(outputs);
      expect(result.outputNames).toBe(null);
    });

    it('should retrieve node with only outputNames (null outputs)', () => {
      const outputNames = ['main'];

      const mockRow = {
        node_type: 'nodes-base.customNode',
        display_name: 'Custom Node',
        description: 'Custom node',
        category: 'misc',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '1',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: null,
        output_names: JSON.stringify(outputNames)
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.customNode');

      expect(result.outputs).toBe(null);
      expect(result.outputNames).toEqual(outputNames);
    });

    it('should retrieve node without outputs or outputNames', () => {
      const mockRow = {
        node_type: 'nodes-base.httpRequest',
        display_name: 'HTTP Request',
        description: 'Make HTTP requests',
        category: 'input',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '4',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: null,
        output_names: null
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.httpRequest');

      expect(result.outputs).toBe(null);
      expect(result.outputNames).toBe(null);
    });

    it('should handle malformed JSON gracefully', () => {
      const mockRow = {
        node_type: 'nodes-base.malformed',
        display_name: 'Malformed Node',
        description: 'Node with malformed JSON',
        category: 'misc',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '1',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: '{invalid json}',
        output_names: '[invalid, json'
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.malformed');

      // Should use default values when JSON parsing fails
      expect(result.outputs).toBe(null);
      expect(result.outputNames).toBe(null);
    });

    it('should return null for non-existent node', () => {
      mockStatement.get.mockReturnValue(null);

      const result = repository.getNode('nodes-base.nonExistent');

      expect(result).toBe(null);
    });

    it('should handle SplitInBatches counterintuitive output order correctly', () => {
      // Test that the output order is preserved: done=0, loop=1
      const outputs = [
        { displayName: 'Done', description: 'Final results when loop completes', index: 0 },
        { displayName: 'Loop', description: 'Current batch data during iteration', index: 1 }
      ];
      const outputNames = ['done', 'loop'];

      const mockRow = {
        node_type: 'nodes-base.splitInBatches',
        display_name: 'Split In Batches',
        description: 'Split data into batches',
        category: 'transform',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '3',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: JSON.stringify(outputs),
        output_names: JSON.stringify(outputNames)
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.splitInBatches');

      // Verify order is preserved
      expect(result.outputs[0].displayName).toBe('Done');
      expect(result.outputs[1].displayName).toBe('Loop');
      expect(result.outputNames[0]).toBe('done');
      expect(result.outputNames[1]).toBe('loop');
    });
  });

  describe('parseNodeRow with outputs', () => {
    it('should parse node row with outputs correctly using parseNodeRow', () => {
      const outputs = [{ displayName: 'Output' }];
      const outputNames = ['main'];

      const mockRow = {
        node_type: 'nodes-base.test',
        display_name: 'Test',
        description: 'Test node',
        category: 'misc',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '1',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: JSON.stringify(outputs),
        output_names: JSON.stringify(outputNames)
      };

      mockStatement.all.mockReturnValue([mockRow]);

      const results = repository.getAllNodes(1);

      expect(results[0].outputs).toEqual(outputs);
      expect(results[0].outputNames).toEqual(outputNames);
    });

    it('should handle empty string as null for outputs', () => {
      const mockRow = {
        node_type: 'nodes-base.empty',
        display_name: 'Empty',
        description: 'Empty node',
        category: 'misc',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '1',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: '', // empty string
        output_names: '' // empty string
      };

      mockStatement.all.mockReturnValue([mockRow]);

      const results = repository.getAllNodes(1);

      // Empty strings should be treated as null since they fail JSON parsing
      expect(results[0].outputs).toBe(null);
      expect(results[0].outputNames).toBe(null);
    });
  });

  describe('complex output structures', () => {
    it('should handle complex output objects with metadata', () => {
      const complexOutputs = [
        {
          displayName: 'Done',
          name: 'done',
          type: 'main',
          hint: 'Receives the final data after all batches have been processed',
          description: 'Final results when loop completes',
          index: 0
        },
        {
          displayName: 'Loop',
          name: 'loop',
          type: 'main', 
          hint: 'Receives the current batch data during each iteration',
          description: 'Current batch data during iteration',
          index: 1
        }
      ];

      const node: ParsedNode = {
        style: 'programmatic',
        nodeType: 'nodes-base.splitInBatches',
        displayName: 'Split In Batches',
        description: 'Split data into batches',
        category: 'transform',
        properties: [],
        credentials: [],
        isAITool: false,
        isTrigger: false,
        isWebhook: false,
        operations: [],
        version: '3',
        isVersioned: false,
        packageName: 'n8n-nodes-base',
        outputs: complexOutputs,
        outputNames: ['done', 'loop']
      };

      repository.saveNode(node);

      // Simulate retrieval
      const mockRow = {
        node_type: 'nodes-base.splitInBatches',
        display_name: 'Split In Batches',
        description: 'Split data into batches',
        category: 'transform',
        development_style: 'programmatic',
        package_name: 'n8n-nodes-base',
        is_ai_tool: 0,
        is_trigger: 0,
        is_webhook: 0,
        is_versioned: 0,
        version: '3',
        properties_schema: JSON.stringify([]),
        operations: JSON.stringify([]),
        credentials_required: JSON.stringify([]),
        documentation: null,
        outputs: JSON.stringify(complexOutputs),
        output_names: JSON.stringify(['done', 'loop'])
      };

      mockStatement.get.mockReturnValue(mockRow);

      const result = repository.getNode('nodes-base.splitInBatches');

      expect(result.outputs).toEqual(complexOutputs);
      expect(result.outputs[0]).toMatchObject({
        displayName: 'Done',
        name: 'done',
        type: 'main',
        hint: 'Receives the final data after all batches have been processed'
      });
    });
  });
});