import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
  workflowNodeSchema,
  workflowConnectionSchema,
  workflowSettingsSchema,
  defaultWorkflowSettings,
  validateWorkflowNode,
  validateWorkflowConnections,
  validateWorkflowSettings,
  cleanWorkflowForCreate,
  cleanWorkflowForUpdate,
  validateWorkflowStructure,
  hasWebhookTrigger,
  getWebhookUrl,
  getWorkflowStructureExample,
  getWorkflowFixSuggestions,
} from '../../../src/services/n8n-validation';
import { WorkflowBuilder } from '../../utils/builders/workflow.builder';
import { z } from 'zod';
import { WorkflowNode, WorkflowConnection, Workflow } from '../../../src/types/n8n-api';

describe('n8n-validation', () => {
  describe('Zod Schemas', () => {
    describe('workflowNodeSchema', () => {
      it('should validate a complete valid node', () => {
        const validNode = {
          id: 'node-1',
          name: 'Test Node',
          type: 'n8n-nodes-base.set',
          typeVersion: 3,
          position: [100, 200],
          parameters: { key: 'value' },
          credentials: { api: 'cred-id' },
          disabled: false,
          notes: 'Test notes',
          notesInFlow: true,
          continueOnFail: true,
          retryOnFail: true,
          maxTries: 3,
          waitBetweenTries: 1000,
          alwaysOutputData: true,
          executeOnce: false,
        };

        const result = workflowNodeSchema.parse(validNode);
        expect(result).toEqual(validNode);
      });

      it('should validate a minimal valid node', () => {
        const minimalNode = {
          id: 'node-1',
          name: 'Test Node',
          type: 'n8n-nodes-base.set',
          typeVersion: 3,
          position: [100, 200],
          parameters: {},
        };

        const result = workflowNodeSchema.parse(minimalNode);
        expect(result).toEqual(minimalNode);
      });

      it('should reject node with missing required fields', () => {
        const invalidNode = {
          name: 'Test Node',
          type: 'n8n-nodes-base.set',
        };

        expect(() => workflowNodeSchema.parse(invalidNode)).toThrow();
      });

      it('should reject node with invalid position format', () => {
        const invalidNode = {
          id: 'node-1',
          name: 'Test Node',
          type: 'n8n-nodes-base.set',
          typeVersion: 3,
          position: [100], // Should be tuple of 2 numbers
          parameters: {},
        };

        expect(() => workflowNodeSchema.parse(invalidNode)).toThrow();
      });

      it('should reject node with invalid type values', () => {
        const invalidNode = {
          id: 'node-1',
          name: 'Test Node',
          type: 'n8n-nodes-base.set',
          typeVersion: '3', // Should be number
          position: [100, 200],
          parameters: {},
        };

        expect(() => workflowNodeSchema.parse(invalidNode)).toThrow();
      });
    });

    describe('workflowConnectionSchema', () => {
      it('should validate valid connections', () => {
        const validConnections = {
          'node-1': {
            main: [[{ node: 'node-2', type: 'main', index: 0 }]],
          },
          'node-2': {
            main: [
              [
                { node: 'node-3', type: 'main', index: 0 },
                { node: 'node-4', type: 'main', index: 0 },
              ],
            ],
          },
        };

        const result = workflowConnectionSchema.parse(validConnections);
        expect(result).toEqual(validConnections);
      });

      it('should validate empty connections', () => {
        const emptyConnections = {};
        const result = workflowConnectionSchema.parse(emptyConnections);
        expect(result).toEqual(emptyConnections);
      });

      it('should reject invalid connection structure', () => {
        const invalidConnections = {
          'node-1': {
            main: [{ node: 'node-2', type: 'main', index: 0 }], // Should be array of arrays
          },
        };

        expect(() => workflowConnectionSchema.parse(invalidConnections)).toThrow();
      });

      it('should reject connections missing required fields', () => {
        const invalidConnections = {
          'node-1': {
            main: [[{ node: 'node-2' }]], // Missing type and index
          },
        };

        expect(() => workflowConnectionSchema.parse(invalidConnections)).toThrow();
      });
    });

    describe('workflowSettingsSchema', () => {
      it('should validate complete settings', () => {
        const completeSettings = {
          executionOrder: 'v1' as const,
          timezone: 'America/New_York',
          saveDataErrorExecution: 'all' as const,
          saveDataSuccessExecution: 'all' as const,
          saveManualExecutions: true,
          saveExecutionProgress: true,
          executionTimeout: 300,
          errorWorkflow: 'error-handler-workflow',
        };

        const result = workflowSettingsSchema.parse(completeSettings);
        expect(result).toEqual(completeSettings);
      });

      it('should apply defaults for missing fields', () => {
        const minimalSettings = {};
        const result = workflowSettingsSchema.parse(minimalSettings);
        
        expect(result).toEqual({
          executionOrder: 'v1',
          saveDataErrorExecution: 'all',
          saveDataSuccessExecution: 'all',
          saveManualExecutions: true,
          saveExecutionProgress: true,
        });
      });

      it('should reject invalid enum values', () => {
        const invalidSettings = {
          executionOrder: 'v2', // Invalid enum value
        };

        expect(() => workflowSettingsSchema.parse(invalidSettings)).toThrow();
      });
    });
  });

  describe('Validation Functions', () => {
    describe('validateWorkflowNode', () => {
      it('should validate and return a valid node', () => {
        const node = {
          id: 'test-1',
          name: 'Test',
          type: 'n8n-nodes-base.webhook',
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: {},
        };

        const result = validateWorkflowNode(node);
        expect(result).toEqual(node);
      });

      it('should throw for invalid node', () => {
        const invalidNode = { name: 'Test' };
        expect(() => validateWorkflowNode(invalidNode)).toThrow();
      });
    });

    describe('validateWorkflowConnections', () => {
      it('should validate and return valid connections', () => {
        const connections = {
          'Node1': {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]],
          },
        };

        const result = validateWorkflowConnections(connections);
        expect(result).toEqual(connections);
      });

      it('should throw for invalid connections', () => {
        const invalidConnections = {
          'Node1': {
            main: 'invalid', // Should be array
          },
        };

        expect(() => validateWorkflowConnections(invalidConnections)).toThrow();
      });
    });

    describe('validateWorkflowSettings', () => {
      it('should validate and return valid settings', () => {
        const settings = {
          executionOrder: 'v1' as const,
          timezone: 'UTC',
        };

        const result = validateWorkflowSettings(settings);
        expect(result).toMatchObject(settings);
      });

      it('should apply defaults and validate', () => {
        const result = validateWorkflowSettings({});
        expect(result).toMatchObject(defaultWorkflowSettings);
      });
    });
  });

  describe('Workflow Cleaning Functions', () => {
    describe('cleanWorkflowForCreate', () => {
      it('should remove read-only fields', () => {
        const workflow = {
          id: 'should-be-removed',
          name: 'Test Workflow',
          nodes: [],
          connections: {},
          createdAt: '2023-01-01',
          updatedAt: '2023-01-01',
          versionId: 'v123',
          meta: { test: 'data' },
          active: true,
          tags: ['tag1'],
        };

        const cleaned = cleanWorkflowForCreate(workflow as any);
        
        expect(cleaned).not.toHaveProperty('id');
        expect(cleaned).not.toHaveProperty('createdAt');
        expect(cleaned).not.toHaveProperty('updatedAt');
        expect(cleaned).not.toHaveProperty('versionId');
        expect(cleaned).not.toHaveProperty('meta');
        expect(cleaned).not.toHaveProperty('active');
        expect(cleaned).not.toHaveProperty('tags');
        expect(cleaned.name).toBe('Test Workflow');
      });

      it('should add default settings if not present', () => {
        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
        };

        const cleaned = cleanWorkflowForCreate(workflow as Workflow);
        expect(cleaned.settings).toEqual(defaultWorkflowSettings);
      });

      it('should preserve existing settings', () => {
        const customSettings = {
          executionOrder: 'v0' as const,
          timezone: 'America/New_York',
        };

        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
          settings: customSettings,
        };

        const cleaned = cleanWorkflowForCreate(workflow as Workflow);
        expect(cleaned.settings).toEqual(customSettings);
      });
    });

    describe('cleanWorkflowForUpdate', () => {
      it('should remove all read-only and computed fields', () => {
        const workflow = {
          id: 'keep-id',
          name: 'Updated Workflow',
          nodes: [],
          connections: {},
          createdAt: '2023-01-01',
          updatedAt: '2023-01-01',
          versionId: 'v123',
          meta: { test: 'data' },
          staticData: { some: 'data' },
          pinData: { pin: 'data' },
          tags: ['tag1'],
          isArchived: false,
          usedCredentials: ['cred1'],
          sharedWithProjects: ['proj1'],
          triggerCount: 5,
          shared: true,
          active: true,
          settings: { executionOrder: 'v1' },
        } as any;

        const cleaned = cleanWorkflowForUpdate(workflow);
        
        // Should remove all these fields
        expect(cleaned).not.toHaveProperty('id');
        expect(cleaned).not.toHaveProperty('createdAt');
        expect(cleaned).not.toHaveProperty('updatedAt');
        expect(cleaned).not.toHaveProperty('versionId');
        expect(cleaned).not.toHaveProperty('meta');
        expect(cleaned).not.toHaveProperty('staticData');
        expect(cleaned).not.toHaveProperty('pinData');
        expect(cleaned).not.toHaveProperty('tags');
        expect(cleaned).not.toHaveProperty('isArchived');
        expect(cleaned).not.toHaveProperty('usedCredentials');
        expect(cleaned).not.toHaveProperty('sharedWithProjects');
        expect(cleaned).not.toHaveProperty('triggerCount');
        expect(cleaned).not.toHaveProperty('shared');
        expect(cleaned).not.toHaveProperty('active');
        
        // Should keep name and filter settings to safe properties
        expect(cleaned.name).toBe('Updated Workflow');
        expect(cleaned.settings).toEqual({ executionOrder: 'v1' });
      });

      it('should add empty settings object for cloud API compatibility', () => {
        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
        } as any;

        const cleaned = cleanWorkflowForUpdate(workflow);
        expect(cleaned.settings).toEqual({});
      });

      it('should filter settings to safe properties to prevent API errors (Issue #248 - final fix)', () => {
        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
          settings: {
            executionOrder: 'v1' as const,
            saveDataSuccessExecution: 'none' as const,
            callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out (not in OpenAPI spec)
            timeSavedPerExecution: 5, // Filtered out (UI-only property)
          },
        } as any;

        const cleaned = cleanWorkflowForUpdate(workflow);

        // Unsafe properties filtered out, safe properties kept
        expect(cleaned.settings).toEqual({
          executionOrder: 'v1',
          saveDataSuccessExecution: 'none'
        });
        expect(cleaned.settings).not.toHaveProperty('callerPolicy');
        expect(cleaned.settings).not.toHaveProperty('timeSavedPerExecution');
      });

      it('should filter out callerPolicy (Issue #248 - API limitation)', () => {
        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
          settings: {
            executionOrder: 'v1' as const,
            callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
            errorWorkflow: 'N2O2nZy3aUiBRGFN',
          },
        } as any;

        const cleaned = cleanWorkflowForUpdate(workflow);

        // callerPolicy filtered out (causes API errors), safe properties kept
        expect(cleaned.settings).toEqual({
          executionOrder: 'v1',
          errorWorkflow: 'N2O2nZy3aUiBRGFN'
        });
        expect(cleaned.settings).not.toHaveProperty('callerPolicy');
      });

      it('should filter all settings properties correctly (Issue #248 - API design)', () => {
        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
          settings: {
            executionOrder: 'v0' as const,
            timezone: 'UTC',
            saveDataErrorExecution: 'all' as const,
            saveDataSuccessExecution: 'none' as const,
            saveManualExecutions: false,
            saveExecutionProgress: false,
            executionTimeout: 300,
            errorWorkflow: 'error-workflow-id',
            callerPolicy: 'workflowsFromAList' as const, // Filtered out (not in OpenAPI spec)
          },
        } as any;

        const cleaned = cleanWorkflowForUpdate(workflow);

        // Safe properties kept, unsafe properties filtered out
        // See: https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916
        expect(cleaned.settings).toEqual({
          executionOrder: 'v0',
          timezone: 'UTC',
          saveDataErrorExecution: 'all',
          saveDataSuccessExecution: 'none',
          saveManualExecutions: false,
          saveExecutionProgress: false,
          executionTimeout: 300,
          errorWorkflow: 'error-workflow-id'
        });
        expect(cleaned.settings).not.toHaveProperty('callerPolicy');
      });

      it('should handle workflows without settings gracefully', () => {
        const workflow = {
          name: 'Test Workflow',
          nodes: [],
          connections: {},
        } as any;

        const cleaned = cleanWorkflowForUpdate(workflow);
        expect(cleaned.settings).toEqual({});
      });
    });
  });

  describe('validateWorkflowStructure', () => {
    it('should return no errors for valid workflow', () => {
      const workflow = new WorkflowBuilder('Valid Workflow')
        .addWebhookNode({ id: 'webhook-1', name: 'Webhook' })
        .addSlackNode({ id: 'slack-1', name: 'Send Slack' })
        .connect('Webhook', 'Send Slack')
        .build();

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toEqual([]);
    });

    it('should detect missing workflow name', () => {
      const workflow = {
        nodes: [],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toContain('Workflow name is required');
    });

    it('should detect missing nodes', () => {
      const workflow = {
        name: 'Test',
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toContain('Workflow must have at least one node');
    });

    it('should detect empty nodes array', () => {
      const workflow = {
        name: 'Test',
        nodes: [],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toContain('Workflow must have at least one node');
    });

    it('should detect missing connections', () => {
      const workflow = {
        name: 'Test',
        nodes: [{ id: 'node-1', name: 'Node 1', type: 'n8n-nodes-base.set', typeVersion: 1, position: [0, 0] as [number, number], parameters: {} }],
      };

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toContain('Workflow connections are required');
    });

    it('should allow single webhook node workflow', () => {
      const workflow = {
        name: 'Webhook Only',
        nodes: [{
          id: 'webhook-1',
          name: 'Webhook',
          type: 'n8n-nodes-base.webhook',
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toEqual([]);
    });

    it('should reject single non-webhook node workflow', () => {
      const workflow = {
        name: 'Invalid Single Node',
        nodes: [{
          id: 'set-1',
          name: 'Set',
          type: 'n8n-nodes-base.set',
          typeVersion: 3,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Single-node workflows are only valid for webhooks. Add at least one more node and connect them. Example: Manual Trigger → Set node');
    });

    it('should detect empty connections in multi-node workflow', () => {
      const workflow = {
        name: 'Disconnected Nodes',
        nodes: [
          {
            id: 'node-1',
            name: 'Node 1',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'node-2',
            name: 'Node 2',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [550, 300] as [number, number],
            parameters: {},
          },
        ],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Multi-node workflow has empty connections. Connect nodes like this: connections: { "Node1 Name": { "main": [[{ "node": "Node2 Name", "type": "main", "index": 0 }]] } }');
    });

    it('should validate node type format - missing package prefix', () => {
      const workflow = {
        name: 'Invalid Node Type',
        nodes: [{
          id: 'node-1',
          name: 'Node 1',
          type: 'webhook', // Missing package prefix
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Invalid node type "webhook" at index 0. Node types must include package prefix (e.g., "n8n-nodes-base.webhook").');
    });

    it('should validate node type format - wrong prefix format', () => {
      const workflow = {
        name: 'Invalid Node Type',
        nodes: [{
          id: 'node-1',
          name: 'Node 1',
          type: 'nodes-base.webhook', // Wrong prefix
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Invalid node type "nodes-base.webhook" at index 0. Use "n8n-nodes-base.webhook" instead.');
    });

    it('should detect invalid node structure', () => {
      const workflow = {
        name: 'Invalid Node',
        nodes: [{
          name: 'Missing Required Fields',
          // Missing id, type, typeVersion, position, parameters
        } as any],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow);
      // The validation will fail because the node is missing required fields
      expect(errors.some(e => e.includes('Invalid node at index 0'))).toBe(true);
    });

    it('should detect non-existent connection source by name', () => {
      const workflow = {
        name: 'Bad Connection',
        nodes: [{
          id: 'node-1',
          name: 'Node 1',
          type: 'n8n-nodes-base.set',
          typeVersion: 3,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {
          'Non-existent Node': {
            main: [[{ node: 'Node 1', type: 'main', index: 0 }]],
          },
        },
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Connection references non-existent node: Non-existent Node');
    });

    it('should detect non-existent connection target by name', () => {
      const workflow = {
        name: 'Bad Connection Target',
        nodes: [{
          id: 'node-1',
          name: 'Node 1',
          type: 'n8n-nodes-base.set',
          typeVersion: 3,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {
          'Node 1': {
            main: [[{ node: 'Non-existent Node', type: 'main', index: 0 }]],
          },
        },
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Connection references non-existent target node: Non-existent Node (from Node 1[0][0])');
    });

    it('should detect when node ID is used instead of name in connection source', () => {
      const workflow = {
        name: 'ID Instead of Name',
        nodes: [
          {
            id: 'node-1',
            name: 'First Node',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'node-2',
            name: 'Second Node',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [550, 300] as [number, number],
            parameters: {},
          },
        ],
        connections: {
          'node-1': { // Using ID instead of name
            main: [[{ node: 'Second Node', type: 'main', index: 0 }]],
          },
        },
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain("Connection uses node ID 'node-1' but must use node name 'First Node'. Change connections.node-1 to connections['First Node']");
    });

    it('should detect when node ID is used instead of name in connection target', () => {
      const workflow = {
        name: 'ID Instead of Name in Target',
        nodes: [
          {
            id: 'node-1',
            name: 'First Node',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'node-2',
            name: 'Second Node',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [550, 300] as [number, number],
            parameters: {},
          },
        ],
        connections: {
          'First Node': {
            main: [[{ node: 'node-2', type: 'main', index: 0 }]], // Using ID instead of name
          },
        },
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain("Connection target uses node ID 'node-2' but must use node name 'Second Node' (from First Node[0][0])");
    });

    it('should handle complex multi-output connections', () => {
      const workflow = {
        name: 'Complex Connections',
        nodes: [
          {
            id: 'if-1',
            name: 'IF Node',
            type: 'n8n-nodes-base.if',
            typeVersion: 2,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'true-1',
            name: 'True Branch',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [450, 200] as [number, number],
            parameters: {},
          },
          {
            id: 'false-1',
            name: 'False Branch',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [450, 400] as [number, number],
            parameters: {},
          },
        ],
        connections: {
          'IF Node': {
            main: [
              [{ node: 'True Branch', type: 'main', index: 0 }],
              [{ node: 'False Branch', type: 'main', index: 0 }],
            ],
          },
        },
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toEqual([]);
    });

    it('should validate invalid connections structure', () => {
      const workflow = {
        name: 'Invalid Connections',
        nodes: [
          {
            id: 'node-1',
            name: 'Node 1',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'node-2',
            name: 'Node 2',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [550, 300] as [number, number],
            parameters: {},
          }
        ],
        connections: {
          'Node 1': 'invalid', // Should be an object
        } as any,
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors.some(e => e.includes('Invalid connections'))).toBe(true);
    });
  });

  describe('hasWebhookTrigger', () => {
    it('should return true for workflow with webhook node', () => {
      const workflow = new WorkflowBuilder()
        .addWebhookNode()
        .build() as Workflow;

      expect(hasWebhookTrigger(workflow)).toBe(true);
    });

    it('should return true for workflow with webhookTrigger node', () => {
      const workflow = {
        name: 'Test',
        nodes: [{
          id: 'webhook-1',
          name: 'Webhook Trigger',
          type: 'n8n-nodes-base.webhookTrigger',
          typeVersion: 1,
          position: [250, 300] as [number, number],
          parameters: {},
        }],
        connections: {},
      } as Workflow;

      expect(hasWebhookTrigger(workflow)).toBe(true);
    });

    it('should return false for workflow without webhook nodes', () => {
      const workflow = new WorkflowBuilder()
        .addSlackNode()
        .addHttpRequestNode()
        .build() as Workflow;

      expect(hasWebhookTrigger(workflow)).toBe(false);
    });

    it('should return true even if webhook is not the first node', () => {
      const workflow = new WorkflowBuilder()
        .addSlackNode()
        .addWebhookNode()
        .addHttpRequestNode()
        .build() as Workflow;

      expect(hasWebhookTrigger(workflow)).toBe(true);
    });
  });

  describe('getWebhookUrl', () => {
    it('should return webhook path from webhook node', () => {
      const workflow = {
        name: 'Test',
        nodes: [{
          id: 'webhook-1',
          name: 'Webhook',
          type: 'n8n-nodes-base.webhook',
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: {
            path: 'my-custom-webhook',
          },
        }],
        connections: {},
      } as Workflow;

      expect(getWebhookUrl(workflow)).toBe('my-custom-webhook');
    });

    it('should return webhook path from webhookTrigger node', () => {
      const workflow = {
        name: 'Test',
        nodes: [{
          id: 'webhook-1',
          name: 'Webhook Trigger',
          type: 'n8n-nodes-base.webhookTrigger',
          typeVersion: 1,
          position: [250, 300] as [number, number],
          parameters: {
            path: 'trigger-webhook-path',
          },
        }],
        connections: {},
      } as Workflow;

      expect(getWebhookUrl(workflow)).toBe('trigger-webhook-path');
    });

    it('should return null if no webhook node exists', () => {
      const workflow = new WorkflowBuilder()
        .addSlackNode()
        .build() as Workflow;

      expect(getWebhookUrl(workflow)).toBe(null);
    });

    it('should return null if webhook node has no parameters', () => {
      const workflow = {
        name: 'Test',
        nodes: [{
          id: 'webhook-1',
          name: 'Webhook',
          type: 'n8n-nodes-base.webhook',
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: undefined as any,
        }],
        connections: {},
      } as Workflow;

      expect(getWebhookUrl(workflow)).toBe(null);
    });

    it('should return null if webhook node has no path parameter', () => {
      const workflow = {
        name: 'Test',
        nodes: [{
          id: 'webhook-1',
          name: 'Webhook',
          type: 'n8n-nodes-base.webhook',
          typeVersion: 2,
          position: [250, 300] as [number, number],
          parameters: {
            method: 'POST',
            // No path parameter
          },
        }],
        connections: {},
      } as Workflow;

      expect(getWebhookUrl(workflow)).toBe(null);
    });

    it('should return first webhook path when multiple webhooks exist', () => {
      const workflow = {
        name: 'Test',
        nodes: [
          {
            id: 'webhook-1',
            name: 'Webhook 1',
            type: 'n8n-nodes-base.webhook',
            typeVersion: 2,
            position: [250, 300] as [number, number],
            parameters: {
              path: 'first-webhook',
            },
          },
          {
            id: 'webhook-2',
            name: 'Webhook 2',
            type: 'n8n-nodes-base.webhook',
            typeVersion: 2,
            position: [550, 300] as [number, number],
            parameters: {
              path: 'second-webhook',
            },
          },
        ],
        connections: {},
      } as Workflow;

      expect(getWebhookUrl(workflow)).toBe('first-webhook');
    });
  });

  describe('getWorkflowStructureExample', () => {
    it('should return a string containing example workflow structure', () => {
      const example = getWorkflowStructureExample();
      
      expect(example).toContain('Minimal Workflow Example');
      expect(example).toContain('Manual Trigger');
      expect(example).toContain('Set Data');
      expect(example).toContain('connections');
      expect(example).toContain('IMPORTANT: In connections, use the node NAME');
    });

    it('should contain valid JSON structure in example', () => {
      const example = getWorkflowStructureExample();
      // Extract the JSON part between the first { and last }
      const match = example.match(/\{[\s\S]*\}/);
      expect(match).toBeTruthy();
      
      if (match) {
        // Should not throw when parsing
        expect(() => JSON.parse(match[0])).not.toThrow();
      }
    });
  });

  describe('getWorkflowFixSuggestions', () => {
    it('should suggest fixes for empty connections', () => {
      const errors = ['Multi-node workflow has empty connections'];
      const suggestions = getWorkflowFixSuggestions(errors);
      
      expect(suggestions).toContain('Add connections between your nodes. Each node (except endpoints) should connect to another node.');
      expect(suggestions).toContain('Connection format: connections: { "Source Node Name": { "main": [[{ "node": "Target Node Name", "type": "main", "index": 0 }]] } }');
    });

    it('should suggest fixes for single-node workflows', () => {
      const errors = ['Single-node workflows are only valid for webhooks'];
      const suggestions = getWorkflowFixSuggestions(errors);
      
      expect(suggestions).toContain('Add at least one more node to process data. Common patterns: Trigger → Process → Output');
      expect(suggestions).toContain('Examples: Manual Trigger → Set, Webhook → HTTP Request, Schedule Trigger → Database Query');
    });

    it('should suggest fixes for node ID usage instead of names', () => {
      const errors = ["Connection uses node ID 'set-1' but must use node name 'Set Data' instead of node name"];
      const suggestions = getWorkflowFixSuggestions(errors);
      
      expect(suggestions.some(s => s.includes('Replace node IDs with node names'))).toBe(true);
      expect(suggestions.some(s => s.includes('connections: { "set-1": {...} }'))).toBe(true);
    });

    it('should return empty array for no errors', () => {
      const suggestions = getWorkflowFixSuggestions([]);
      expect(suggestions).toEqual([]);
    });

    it('should handle multiple error types', () => {
      const errors = [
        'Multi-node workflow has empty connections',
        'Single-node workflows are only valid for webhooks',
        "Connection uses node ID instead of node name",
      ];
      const suggestions = getWorkflowFixSuggestions(errors);
      
      expect(suggestions.length).toBeGreaterThan(3);
      expect(suggestions).toContain('Add connections between your nodes. Each node (except endpoints) should connect to another node.');
      expect(suggestions).toContain('Add at least one more node to process data. Common patterns: Trigger → Process → Output');
      expect(suggestions).toContain('Replace node IDs with node names in connections. The name is what appears in the node header.');
    });

    it('should not duplicate suggestions for similar errors', () => {
      const errors = [
        "Connection uses node ID 'id1' instead of node name",
        "Connection uses node ID 'id2' instead of node name",
      ];
      const suggestions = getWorkflowFixSuggestions(errors);
      
      // Should only have 2 suggestions for this error type
      const idSuggestions = suggestions.filter(s => s.includes('Replace node IDs'));
      expect(idSuggestions.length).toBe(1);
    });
  });

  describe('Edge Cases and Error Conditions', () => {
    it('should handle workflow with null values gracefully', () => {
      const workflow = {
        name: 'Test',
        nodes: null as any,
        connections: null as any,
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Workflow must have at least one node');
      expect(errors).toContain('Workflow connections are required');
    });

    it('should handle undefined parameters in cleaning functions', () => {
      const workflow = {
        name: undefined as any,
        nodes: undefined as any,
        connections: undefined as any,
      };

      expect(() => cleanWorkflowForCreate(workflow)).not.toThrow();
      expect(() => cleanWorkflowForUpdate(workflow as any)).not.toThrow();
    });

    it('should handle circular references in workflow structure', () => {
      const node1: any = {
        id: 'node-1',
        name: 'Node 1',
        type: 'n8n-nodes-base.set',
        typeVersion: 3,
        position: [250, 300],
        parameters: {},
      };
      
      // Create circular reference
      node1.parameters.circular = node1;

      const workflow = {
        name: 'Circular Ref',
        nodes: [node1],
        connections: {},
      };

      // Should handle circular references without crashing
      expect(() => validateWorkflowStructure(workflow)).not.toThrow();
    });

    it('should validate very large position values', () => {
      const node = {
        id: 'node-1',
        name: 'Test Node',
        type: 'n8n-nodes-base.set',
        typeVersion: 3,
        position: [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER] as [number, number],
        parameters: {},
      };

      expect(() => validateWorkflowNode(node)).not.toThrow();
    });

    it('should handle special characters in node names', () => {
      const workflow = {
        name: 'Special Chars',
        nodes: [
          {
            id: 'node-1',
            name: 'Node with "quotes" & special <chars>',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'node-2',
            name: 'Normal Node',
            type: 'n8n-nodes-base.set',
            typeVersion: 3,
            position: [550, 300] as [number, number],
            parameters: {},
          },
        ],
        connections: {
          'Node with "quotes" & special <chars>': {
            main: [[{ node: 'Normal Node', type: 'main', index: 0 }]],
          },
        },
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toEqual([]);
    });

    it('should handle empty string values', () => {
      const workflow = {
        name: '',
        nodes: [{
          id: '',
          name: '',
          type: '',
          typeVersion: 1,
          position: [0, 0] as [number, number],
          parameters: {},
        }],
        connections: {},
      };

      const errors = validateWorkflowStructure(workflow);
      expect(errors).toContain('Workflow name is required');
      // Empty string for type will be caught as invalid
      expect(errors.some(e => e.includes('Invalid node at index 0') || e.includes('Node types must include package prefix'))).toBe(true);
    });

    it('should handle negative position values', () => {
      const node = {
        id: 'node-1',
        name: 'Test Node',
        type: 'n8n-nodes-base.set',
        typeVersion: 3,
        position: [-100, -200] as [number, number],
        parameters: {},
      };

      // Negative positions are valid
      expect(() => validateWorkflowNode(node)).not.toThrow();
    });

    it('should validate settings with additional unknown properties', () => {
      const settings = {
        executionOrder: 'v1' as const,
        timezone: 'UTC',
        unknownProperty: 'should be allowed',
        anotherUnknown: { nested: 'object' },
      };

      // Zod by default strips unknown properties
      const result = validateWorkflowSettings(settings);
      expect(result).toHaveProperty('executionOrder', 'v1');
      expect(result).toHaveProperty('timezone', 'UTC');
      expect(result).not.toHaveProperty('unknownProperty');
      expect(result).not.toHaveProperty('anotherUnknown');
    });
  });

  describe('Integration Tests', () => {
    it('should validate a complete real-world workflow', () => {
      const workflow = new WorkflowBuilder('Production Workflow')
        .addWebhookNode({ 
          id: 'webhook-1', 
          name: 'Order Webhook',
          parameters: {
            path: 'new-order',
            method: 'POST',
          },
        })
        .addIfNode({
          id: 'if-1',
          name: 'Check Order Value',
          parameters: {
            conditions: {
              options: { caseSensitive: true, leftValue: '', typeValidation: 'strict' },
              conditions: [{
                id: '1',
                leftValue: '={{ $json.orderValue }}',
                rightValue: '100',
                operator: { type: 'number', operation: 'gte' },
              }],
              combinator: 'and',
            },
          },
        })
        .addSlackNode({
          id: 'slack-1',
          name: 'Notify High Value',
          parameters: {
            channel: '#high-value-orders',
            text: 'High value order received: ${{ $json.orderId }}',
          },
        })
        .addHttpRequestNode({
          id: 'http-1',
          name: 'Update Inventory',
          parameters: {
            method: 'POST',
            url: 'https://api.inventory.com/update',
            sendBody: true,
            bodyParametersJson: '={{ $json }}',
          },
        })
        .connect('Order Webhook', 'Check Order Value')
        .connect('Check Order Value', 'Notify High Value', 0) // True output
        .connect('Check Order Value', 'Update Inventory', 1) // False output
        .setSettings({
          executionOrder: 'v1',
          timezone: 'America/New_York',
          saveDataErrorExecution: 'all',
          saveDataSuccessExecution: 'none',
          executionTimeout: 300,
        })
        .build();

      const errors = validateWorkflowStructure(workflow as any);
      expect(errors).toEqual([]);

      // Validate individual components
      workflow.nodes.forEach(node => {
        expect(() => validateWorkflowNode(node)).not.toThrow();
      });
      expect(() => validateWorkflowConnections(workflow.connections)).not.toThrow();
      expect(() => validateWorkflowSettings(workflow.settings!)).not.toThrow();
    });

    it('should clean and validate workflow for API operations', () => {
      const originalWorkflow = {
        id: 'wf-123',
        name: 'API Test Workflow',
        nodes: [
          {
            id: 'manual-1',
            name: 'Manual Trigger',
            type: 'n8n-nodes-base.manualTrigger',
            typeVersion: 1,
            position: [250, 300] as [number, number],
            parameters: {},
          },
          {
            id: 'set-1',
            name: 'Set Data',
            type: 'n8n-nodes-base.set',
            typeVersion: 3.4,
            position: [450, 300] as [number, number],
            parameters: {
              mode: 'manual',
              assignments: {
                assignments: [{
                  id: '1',
                  name: 'testKey',
                  value: 'testValue',
                  type: 'string',
                }],
              },
            },
          }
        ],
        connections: {
          'Manual Trigger': {
            main: [[{
              node: 'Set Data',
              type: 'main',
              index: 0,
            }]],
          },
        },
        createdAt: '2023-01-01T00:00:00Z',
        updatedAt: '2023-01-02T00:00:00Z',
        versionId: 'v123',
        active: true,
        tags: ['test', 'api'],
        meta: { instanceId: 'instance-123' },
      };

      // Test create cleaning
      const forCreate = cleanWorkflowForCreate(originalWorkflow);
      expect(forCreate).not.toHaveProperty('id');
      expect(forCreate).not.toHaveProperty('createdAt');
      expect(forCreate).not.toHaveProperty('updatedAt');
      expect(forCreate).not.toHaveProperty('versionId');
      expect(forCreate).not.toHaveProperty('active');
      expect(forCreate).not.toHaveProperty('tags');
      expect(forCreate).not.toHaveProperty('meta');
      expect(forCreate).toHaveProperty('settings');
      expect(validateWorkflowStructure(forCreate)).toEqual([]);

      // Test update cleaning
      const forUpdate = cleanWorkflowForUpdate(originalWorkflow as any);
      expect(forUpdate).not.toHaveProperty('id');
      expect(forUpdate).not.toHaveProperty('createdAt');
      expect(forUpdate).not.toHaveProperty('updatedAt');
      expect(forUpdate).not.toHaveProperty('versionId');
      expect(forUpdate).not.toHaveProperty('active');
      expect(forUpdate).not.toHaveProperty('tags');
      expect(forUpdate).not.toHaveProperty('meta');
      expect(forUpdate.settings).toEqual({}); // Settings replaced with empty object for API compatibility
      expect(validateWorkflowStructure(forUpdate)).toEqual([]);
    });
  });
});