import { describe, it, expect, vi, beforeEach } from 'vitest';
import { WorkflowAutoFixer } from '@/services/workflow-auto-fixer';
import { NodeRepository } from '@/database/node-repository';
import type { WorkflowValidationResult } from '@/services/workflow-validator';
import type { Workflow, WorkflowNode } from '@/types/n8n-api';

vi.mock('@/database/node-repository');
vi.mock('@/services/node-similarity-service');

describe('WorkflowAutoFixer - Connection Fixes', () => {
  let autoFixer: WorkflowAutoFixer;
  let mockRepository: NodeRepository;

  const createMockWorkflow = (
    nodes: WorkflowNode[],
    connections: any = {}
  ): Workflow => ({
    id: 'test-workflow',
    name: 'Test Workflow',
    active: false,
    nodes,
    connections,
    settings: {},
    createdAt: '',
    updatedAt: ''
  });

  const createMockNode = (id: string, name: string, type: string = 'n8n-nodes-base.noOp'): WorkflowNode => ({
    id,
    name,
    type,
    typeVersion: 1,
    position: [0, 0],
    parameters: {}
  });

  const emptyValidation: WorkflowValidationResult = {
    valid: true,
    errors: [],
    warnings: [],
    statistics: {
      totalNodes: 0,
      enabledNodes: 0,
      triggerNodes: 0,
      validConnections: 0,
      invalidConnections: 0,
      expressionsValidated: 0
    },
    suggestions: []
  };

  beforeEach(() => {
    vi.clearAllMocks();
    mockRepository = new NodeRepository({} as any);
    vi.spyOn(mockRepository, 'getNodeVersions').mockReturnValue([]);
    autoFixer = new WorkflowAutoFixer(mockRepository);
  });

  describe('Numeric Keys', () => {
    it('should convert single numeric key to main[index]', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            '0': [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-numeric-keys');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].before).toBe('0');
      expect(connFixes[0].after).toBe('main[0]');

      // Verify replaceConnections operation
      const replaceOp = result.operations.find(op => op.type === 'replaceConnections');
      expect(replaceOp).toBeDefined();
      const connOp = replaceOp as any;
      expect(connOp.connections.Node1['main']).toBeDefined();
      expect(connOp.connections.Node1['0']).toBeUndefined();
    });

    it('should convert multiple numeric keys', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2'), createMockNode('id3', 'Node3')],
        {
          Node1: {
            '0': [[{ node: 'Node2', type: 'main', index: 0 }]],
            '1': [[{ node: 'Node3', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-numeric-keys');
      expect(connFixes).toHaveLength(2);
    });

    it('should merge with existing main entries', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2'), createMockNode('id3', 'Node3')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]],
            '1': [[{ node: 'Node3', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const replaceOp = result.operations.find(op => op.type === 'replaceConnections') as any;
      expect(replaceOp.connections.Node1['main']).toHaveLength(2);
      expect(replaceOp.connections.Node1['main'][0]).toEqual([{ node: 'Node2', type: 'main', index: 0 }]);
      expect(replaceOp.connections.Node1['main'][1]).toEqual([{ node: 'Node3', type: 'main', index: 0 }]);
    });

    it('should handle sparse numeric keys with gap filling', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2'), createMockNode('id3', 'Node3')],
        {
          Node1: {
            '0': [[{ node: 'Node2', type: 'main', index: 0 }]],
            '3': [[{ node: 'Node3', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const replaceOp = result.operations.find(op => op.type === 'replaceConnections') as any;
      expect(replaceOp.connections.Node1['main']).toHaveLength(4);
      expect(replaceOp.connections.Node1['main'][1]).toEqual([]);
      expect(replaceOp.connections.Node1['main'][2]).toEqual([]);
    });
  });

  describe('Invalid Type', () => {
    it('should fix numeric type to "main"', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: '0', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-invalid-type');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].before).toBe('0');
      expect(connFixes[0].after).toBe('main');
    });

    it('should use parent output key for AI connection types', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            ai_tool: [[{ node: 'Node2', type: '0', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-invalid-type');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].after).toBe('ai_tool');
    });
  });

  describe('ID-to-Name', () => {
    it('should replace source key when it matches a node ID', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('abc-123', 'Node1'), createMockNode('def-456', 'Node2')],
        {
          'abc-123': {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-id-to-name');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].before).toBe('abc-123');
      expect(connFixes[0].after).toBe('Node1');

      const replaceOp = result.operations.find(op => op.type === 'replaceConnections') as any;
      expect(replaceOp.connections['Node1']).toBeDefined();
      expect(replaceOp.connections['abc-123']).toBeUndefined();
    });

    it('should replace target node value when it matches a node ID', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('abc-123', 'Node1'), createMockNode('def-456', 'Node2')],
        {
          Node1: {
            main: [[{ node: 'def-456', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-id-to-name');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].before).toBe('def-456');
      expect(connFixes[0].after).toBe('Node2');
    });

    it('should NOT fix when key matches both an ID and a name', async () => {
      // Node with name that looks like an ID of another node
      const workflow = createMockWorkflow(
        [createMockNode('abc-123', 'abc-123'), createMockNode('def-456', 'Node2')],
        {
          'abc-123': {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-id-to-name');
      expect(connFixes).toHaveLength(0);
    });
  });

  describe('Dedup', () => {
    it('should remove exact duplicate connections', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[
              { node: 'Node2', type: 'main', index: 0 },
              { node: 'Node2', type: 'main', index: 0 },
            ]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-duplicate-removal');
      expect(connFixes).toHaveLength(1);

      const replaceOp = result.operations.find(op => op.type === 'replaceConnections') as any;
      expect(replaceOp.connections.Node1.main[0]).toHaveLength(1);
    });

    it('should keep near-duplicates with different index', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[
              { node: 'Node2', type: 'main', index: 0 },
              { node: 'Node2', type: 'main', index: 1 },
            ]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-duplicate-removal');
      expect(connFixes).toHaveLength(0);
    });
  });

  describe('Input Index', () => {
    it('should reset to 0 for single-input nodes', async () => {
      const validation: WorkflowValidationResult = {
        ...emptyValidation,
        errors: [{
          type: 'error',
          nodeName: 'Node2',
          message: 'Input index 3 on node "Node2" exceeds its input count (1). Connection from "Node1" targets input 3, but this node has 1 main input(s) (indices 0-0).',
          code: 'INPUT_INDEX_OUT_OF_BOUNDS'
        }]
      };

      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2', 'n8n-nodes-base.httpRequest')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 3 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, validation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-input-index');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].before).toBe(3);
      expect(connFixes[0].after).toBe(0);
      expect(connFixes[0].confidence).toBe('medium');
    });

    it('should clamp for Merge nodes', async () => {
      const validation: WorkflowValidationResult = {
        ...emptyValidation,
        errors: [{
          type: 'error',
          nodeName: 'MergeNode',
          message: 'Input index 5 on node "MergeNode" exceeds its input count (2). Connection from "Node1" targets input 5, but this node has 2 main input(s) (indices 0-1).',
          code: 'INPUT_INDEX_OUT_OF_BOUNDS'
        }]
      };

      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'MergeNode', 'n8n-nodes-base.merge')],
        {
          Node1: {
            main: [[{ node: 'MergeNode', type: 'main', index: 5 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, validation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-input-index');
      expect(connFixes).toHaveLength(1);
      expect(connFixes[0].before).toBe(5);
      expect(connFixes[0].after).toBe(1); // clamped to max valid index
    });

    it('should not fix valid indices', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-input-index');
      expect(connFixes).toHaveLength(0);
    });
  });

  describe('Combined', () => {
    it('should fix multiple issues in one workflow', async () => {
      const workflow = createMockWorkflow(
        [
          createMockNode('id1', 'Node1'),
          createMockNode('id2', 'Node2'),
          createMockNode('id3', 'Node3')
        ],
        {
          Node1: {
            '0': [[
              { node: 'Node2', type: '0', index: 0 },
              { node: 'Node2', type: '0', index: 0 }, // duplicate
            ]]
          },
          'id3': { // ID instead of name
            main: [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      expect(result.fixes.length).toBeGreaterThan(0);
      expect(result.operations.find(op => op.type === 'replaceConnections')).toBeDefined();

      // Should have numeric key, invalid type, dedup, and id-to-name fixes
      const types = new Set(result.fixes.map(f => f.type));
      expect(types.has('connection-numeric-keys')).toBe(true);
      expect(types.has('connection-id-to-name')).toBe(true);
    });

    it('should be idempotent (no fixes on valid connections)', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connectionFixTypes = [
        'connection-numeric-keys',
        'connection-invalid-type',
        'connection-id-to-name',
        'connection-duplicate-removal',
        'connection-input-index'
      ];
      const connFixes = result.fixes.filter(f => connectionFixTypes.includes(f.type));
      expect(connFixes).toHaveLength(0);
      expect(result.operations.find(op => op.type === 'replaceConnections')).toBeUndefined();
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty connections', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1')],
        {}
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      expect(result.operations.find(op => op.type === 'replaceConnections')).toBeUndefined();
    });

    it('should respect fixTypes filtering', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            '0': [[{ node: 'Node2', type: '0', index: 0 }]]
          }
        }
      );

      // Only allow numeric key fixes, not invalid type fixes
      const result = await autoFixer.generateFixes(workflow, emptyValidation, [], {
        fixTypes: ['connection-numeric-keys']
      });

      const numericFixes = result.fixes.filter(f => f.type === 'connection-numeric-keys');
      const typeFixes = result.fixes.filter(f => f.type === 'connection-invalid-type');
      expect(numericFixes.length).toBeGreaterThan(0);
      expect(typeFixes).toHaveLength(0);
    });

    it('should filter replaceConnections from operations when confidence threshold filters all connection fixes', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 5 }]]
          }
        }
      );

      const validation: WorkflowValidationResult = {
        ...emptyValidation,
        errors: [{
          type: 'error',
          nodeName: 'Node2',
          message: 'Input index 5 on node "Node2" exceeds its input count (1). Connection from "Node1" targets input 5, but this node has 1 main input(s) (indices 0-0).',
          code: 'INPUT_INDEX_OUT_OF_BOUNDS'
        }]
      };

      // Input index fixes are medium confidence. Filter to high only.
      const result = await autoFixer.generateFixes(workflow, validation, [], {
        confidenceThreshold: 'high'
      });

      // Medium confidence fixes should be filtered out
      const connFixes = result.fixes.filter(f => f.type === 'connection-input-index');
      expect(connFixes).toHaveLength(0);
      expect(result.operations.find(op => op.type === 'replaceConnections')).toBeUndefined();
    });

    it('should include connection issues in summary', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            '0': [[{ node: 'Node2', type: 'main', index: 0 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      expect(result.summary).toContain('connection');
    });

    it('should handle non-existent target nodes gracefully', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1')],
        {
          Node1: {
            '0': [[{ node: 'NonExistent', type: 'main', index: 0 }]]
          }
        }
      );

      // Should not throw
      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      expect(result.fixes.some(f => f.type === 'connection-numeric-keys')).toBe(true);
    });

    it('should skip unparseable INPUT_INDEX_OUT_OF_BOUNDS errors gracefully', async () => {
      const validation: WorkflowValidationResult = {
        ...emptyValidation,
        errors: [{
          type: 'error',
          nodeName: 'Node2',
          message: 'Something unexpected about input indices',
          code: 'INPUT_INDEX_OUT_OF_BOUNDS'
        }]
      };

      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 5 }]]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, validation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-input-index');
      expect(connFixes).toHaveLength(0);
    });

    it('should fix both source keys and target .node values as IDs in the same workflow', async () => {
      const workflow = createMockWorkflow(
        [
          createMockNode('abc-123', 'Node1'),
          createMockNode('def-456', 'Node2'),
          createMockNode('ghi-789', 'Node3')
        ],
        {
          'abc-123': {  // source key is ID
            main: [[{ node: 'def-456', type: 'main', index: 0 }]]  // target .node is also ID
          },
          Node2: {
            main: [[{ node: 'ghi-789', type: 'main', index: 0 }]]  // another target ID
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const connFixes = result.fixes.filter(f => f.type === 'connection-id-to-name');

      // Should fix: source key abc-123 → Node1, target def-456 → Node2, target ghi-789 → Node3
      expect(connFixes).toHaveLength(3);

      const replaceOp = result.operations.find(op => op.type === 'replaceConnections') as any;
      expect(replaceOp.connections['Node1']).toBeDefined();
      expect(replaceOp.connections['abc-123']).toBeUndefined();

      // Verify target .node values were also replaced
      const node1Conns = replaceOp.connections['Node1'].main[0];
      expect(node1Conns[0].node).toBe('Node2');

      const node2Conns = replaceOp.connections['Node2'].main[0];
      expect(node2Conns[0].node).toBe('Node3');
    });

    it('should lower confidence to medium when merging numeric key into non-empty main slot', async () => {
      const workflow = createMockWorkflow(
        [createMockNode('id1', 'Node1'), createMockNode('id2', 'Node2'), createMockNode('id3', 'Node3')],
        {
          Node1: {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]],
            '0': [[{ node: 'Node3', type: 'main', index: 0 }]]  // conflicts with existing main[0]
          }
        }
      );

      const result = await autoFixer.generateFixes(workflow, emptyValidation, []);
      const numericFixes = result.fixes.filter(f => f.type === 'connection-numeric-keys');
      expect(numericFixes).toHaveLength(1);
      expect(numericFixes[0].confidence).toBe('medium');
      expect(numericFixes[0].description).toContain('Merged');
    });
  });
});
