import {beforeEach, describe, expect, it, vi} from 'vitest';
import {StepExecutionStatus, WorkflowExecutionStatus, WorkflowStepType} from '@plunk/db';
import {toPrismaJson} from '@plunk/types';
import {WorkflowExecutionService} from '../WorkflowExecutionService';
import {factories, getPrismaClient} from '../../../../../test/helpers';

vi.mock('node:dns/promises', () => ({
  default: {
    lookup: vi.fn(async () => ({address: '1.2.3.4', family: 4})),
  },
}));

/**
 * Integration Tests: Workflow Execution Engine
 *
 * These tests verify the actual execution logic of workflows,
 * testing real step processing, conditional branching, event handling,
 * and complex multi-step scenarios.
 */

// Mock QueueService to prevent actual job queueing
vi.mock('../QueueService', () => ({
  QueueService: {
    queueWorkflowStep: vi.fn(async () => ({id: 'mock-job-id'})),
    queueEmail: vi.fn(async () => ({id: 'mock-email-job-id'})),
    queueWorkflowTimeout: vi.fn(async () => ({id: 'mock-timeout-job-id'})),
    cancelWorkflowTimeout: vi.fn(async () => true),
  },
}));

// Mock SES for email sending
vi.mock('../../services/ses', () => ({
  ses: {
    sendEmail: vi.fn(async () => ({MessageId: 'mock-message-id'})),
  },
}));

describe('WorkflowExecutionService - Integration Tests', () => {
  let projectId: string;
  const prisma = getPrismaClient();

  beforeEach(async () => {
    const {project} = await factories.createUserWithProject();
    projectId = project.id;
  });

  // ========================================
  // CONDITIONAL BRANCHING (CONDITION STEPS)
  // ========================================
  describe('Conditional Branching', () => {
    it('should follow YES branch when condition evaluates to true', async () => {
      const contact = await factories.createContact({
        projectId,
        data: {isPremium: true},
      });

      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      // Create condition step
      const conditionStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check Premium Status',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            field: 'data.isPremium',
            operator: 'equals',
            value: true,
          }),
        },
      });

      // Create YES and NO branches
      const yesStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Premium Path',
          position: {x: 200, y: -50},
          config: toPrismaJson({reason: 'Premium customer'}),
        },
      });

      const noStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Standard Path',
          position: {x: 200, y: 50},
          config: toPrismaJson({reason: 'Standard customer'}),
        },
      });

      // Create transitions
      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: conditionStep.id},
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: conditionStep.id,
          toStepId: yesStep.id,
          condition: toPrismaJson({branch: 'yes'}),
          priority: 1,
        },
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: conditionStep.id,
          toStepId: noStep.id,
          condition: toPrismaJson({branch: 'no'}),
          priority: 2,
        },
      });

      // Create execution
      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      // Process trigger step
      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);

      // Process condition step
      await WorkflowExecutionService.processStepExecution(execution.id, conditionStep.id);

      // Verify YES branch was taken
      const stepExecutions = await prisma.workflowStepExecution.findMany({
        where: {executionId: execution.id},
        include: {step: true},
        orderBy: {createdAt: 'asc'},
      });

      // Should have: TRIGGER, CONDITION, and YES step
      expect(stepExecutions.length).toBeGreaterThanOrEqual(2);
      const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
      expect(conditionExec).toBeDefined();
      expect(conditionExec?.status).toBe(StepExecutionStatus.COMPLETED);
      expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('yes');
    });

    it('should follow NO branch when condition evaluates to false', async () => {
      const contact = await factories.createContact({
        projectId,
        data: {isPremium: false},
      });

      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      const conditionStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check Premium',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            field: 'data.isPremium',
            operator: 'equals',
            value: true,
          }),
        },
      });

      const yesStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Premium',
          position: {x: 200, y: -50},
          config: toPrismaJson({}),
        },
      });

      const noStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Standard',
          position: {x: 200, y: 50},
          config: toPrismaJson({}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: conditionStep.id},
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: conditionStep.id,
          toStepId: yesStep.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: conditionStep.id,
          toStepId: noStep.id,
          condition: toPrismaJson({branch: 'no'}),
        },
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, conditionStep.id);

      const stepExecutions = await prisma.workflowStepExecution.findMany({
        where: {executionId: execution.id},
        include: {step: true},
        orderBy: {createdAt: 'asc'},
      });

      const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
      expect(conditionExec).toBeDefined();
      expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('no');
    });

    it('should handle complex nested conditions', async () => {
      const contact = await factories.createContact({
        projectId,
        data: {country: 'US', isPremium: true},
      });

      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      // First condition: Check country
      const condition1 = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check Country',
          position: {x: 100, y: 0},
          config: toPrismaJson({field: 'data.country', operator: 'equals', value: 'US'}),
        },
      });

      // Second condition (nested): Check premium status (only for US)
      const condition2 = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check Premium (US)',
          position: {x: 200, y: -50},
          config: toPrismaJson({field: 'data.isPremium', operator: 'equals', value: true}),
        },
      });

      const usPremiumExit = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'US Premium',
          position: {x: 300, y: -75},
          config: toPrismaJson({}),
        },
      });

      const usStandardExit = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'US Standard',
          position: {x: 300, y: -25},
          config: toPrismaJson({}),
        },
      });

      const nonUsExit = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Non-US',
          position: {x: 200, y: 50},
          config: toPrismaJson({}),
        },
      });

      // Create transitions
      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: condition1.id},
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition1.id,
          toStepId: condition2.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition1.id,
          toStepId: nonUsExit.id,
          condition: toPrismaJson({branch: 'no'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition2.id,
          toStepId: usPremiumExit.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition2.id,
          toStepId: usStandardExit.id,
          condition: toPrismaJson({branch: 'no'}),
        },
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, condition1.id);
      await WorkflowExecutionService.processStepExecution(execution.id, condition2.id);

      const stepExecutions = await prisma.workflowStepExecution.findMany({
        where: {executionId: execution.id},
        include: {step: true},
        orderBy: {createdAt: 'asc'},
      });

      // Should have executed: TRIGGER → CONDITION (US) → CONDITION (Premium) → EXIT (US Premium)
      expect(stepExecutions.length).toBeGreaterThanOrEqual(3);
      const conditions = stepExecutions.filter(se => se.step.type === WorkflowStepType.CONDITION);
      expect(conditions).toHaveLength(2);
      expect((conditions[0].output as {branch?: string} | null)?.branch).toBe('yes'); // US = yes
      expect((conditions[1].output as {branch?: string} | null)?.branch).toBe('yes'); // Premium = yes
    });
  });

  // ========================================
  // WAIT_FOR_EVENT STEPS
  // ========================================
  describe('Wait for Event', () => {
    it('should pause workflow execution when waiting for event', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      const waitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.WAIT_FOR_EVENT,
          name: 'Wait for Purchase',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            eventName: 'purchase.completed',
            timeout: 3600, // 1 hour
          }),
        },
      });

      const exitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Complete',
          position: {x: 200, y: 0},
          config: toPrismaJson({}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: waitStep.id},
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: waitStep.id, toStepId: exitStep.id},
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, waitStep.id);

      // Verify step is in WAITING status
      const waitStepExecution = await prisma.workflowStepExecution.findFirst({
        where: {
          executionId: execution.id,
          stepId: waitStep.id,
        },
      });

      expect(waitStepExecution?.status).toBe(StepExecutionStatus.WAITING);

      // Verify workflow execution is in WAITING status
      const updatedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(updatedExecution?.status).toBe(WorkflowExecutionStatus.WAITING);
    });

    it('should resume workflow when expected event arrives', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      const waitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.WAIT_FOR_EVENT,
          name: 'Wait for Event',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            eventName: 'user.verified',
            timeout: 3600,
          }),
        },
      });

      const exitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Done',
          position: {x: 200, y: 0},
          config: toPrismaJson({}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: waitStep.id},
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: waitStep.id, toStepId: exitStep.id},
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, waitStep.id);

      // Verify waiting state
      const waitingExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });
      expect(waitingExecution?.status).toBe(WorkflowExecutionStatus.WAITING);

      // Simulate event arrival by calling handleEvent
      await WorkflowExecutionService.handleEvent(projectId, 'user.verified', contact.id, {verified: true});

      // Verify step execution was resumed
      const waitStepExecution = await prisma.workflowStepExecution.findFirst({
        where: {
          executionId: execution.id,
          stepId: waitStep.id,
        },
      });

      // Step should be completed after event arrives
      expect(waitStepExecution?.status).toBe(StepExecutionStatus.COMPLETED);
    });
  });

  // ========================================
  // COMPLEX MULTI-STEP WORKFLOWS
  // ========================================
  describe('Complex Multi-Step Workflows', () => {
    it('should execute linear workflow end-to-end', async () => {
      const contact = await factories.createContact({projectId});

      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      // Build: TRIGGER → DELAY → CONDITION → EXIT
      const delay = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.DELAY,
          name: 'Wait 1 day',
          position: {x: 100, y: 0},
          config: toPrismaJson({amount: 1, unit: 'days'}),
        },
      });

      const condition = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check Status',
          position: {x: 200, y: 0},
          config: toPrismaJson({field: 'contact.subscribed', operator: 'equals', value: true}),
        },
      });

      const exit = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Complete',
          position: {x: 300, y: 0},
          config: toPrismaJson({}),
        },
      });

      // Create transitions
      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: delay.id},
      });
      await prisma.workflowTransition.create({
        data: {fromStepId: delay.id, toStepId: condition.id},
      });
      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition.id,
          toStepId: exit.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      // Execute through workflow
      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);

      // Verify step executions were created
      const stepExecutions = await prisma.workflowStepExecution.findMany({
        where: {executionId: execution.id},
        include: {step: true},
        orderBy: {createdAt: 'asc'},
      });

      // Should have at least TRIGGER step
      expect(stepExecutions.length).toBeGreaterThanOrEqual(1);

      // Verify trigger step completed
      const triggerExec = stepExecutions.find(se => se.step.type === WorkflowStepType.TRIGGER);
      expect(triggerExec?.status).toBe(StepExecutionStatus.COMPLETED);
    });

    it('should handle workflows with multiple branches that converge', async () => {
      const contact = await factories.createContact({
        projectId,
        data: {segment: 'A'},
      });

      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      // Split into A/B paths, then merge
      const condition = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'A/B Split',
          position: {x: 100, y: 0},
          config: toPrismaJson({field: 'data.segment', operator: 'equals', value: 'A'}),
        },
      });

      const pathA = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.DELAY,
          name: 'Path A Delay',
          position: {x: 200, y: -50},
          config: toPrismaJson({amount: 1, unit: 'hours'}),
        },
      });

      const pathB = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.DELAY,
          name: 'Path B Delay',
          position: {x: 200, y: 50},
          config: toPrismaJson({amount: 2, unit: 'hours'}),
        },
      });

      const merge = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Merge Point',
          position: {x: 300, y: 0},
          config: toPrismaJson({}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: condition.id},
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition.id,
          toStepId: pathA.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition.id,
          toStepId: pathB.id,
          condition: toPrismaJson({branch: 'no'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: pathA.id, toStepId: merge.id},
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: pathB.id, toStepId: merge.id},
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, condition.id);

      const stepExecutions = await prisma.workflowStepExecution.findMany({
        where: {executionId: execution.id},
        include: {step: true},
        orderBy: {createdAt: 'asc'},
      });

      // Should have: TRIGGER, CONDITION, and Path A (since segment = 'A')
      expect(stepExecutions.length).toBeGreaterThanOrEqual(2);
      const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
      expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('yes');
    });
  });

  // ========================================
  // ERROR HANDLING
  // ========================================
  describe('Error Handling', () => {
    it('should mark workflow as FAILED when step execution fails', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      // Create a CONDITION step with invalid config (will fail)
      const badStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Bad Condition',
          position: {x: 100, y: 0},
          config: toPrismaJson({}), // Invalid - missing required fields
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: badStep.id},
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      // Processing the trigger step will automatically try to process the bad step
      // due to the transition, which will throw an error
      await expect(WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id)).rejects.toThrow();

      // Verify workflow execution is marked as FAILED
      const failedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(failedExecution?.status).toBe(WorkflowExecutionStatus.FAILED);
    });

    it('should handle missing contact data gracefully in CONDITION steps', async () => {
      const contact = await factories.createContact({
        projectId,
        data: {}, // No fields
      });

      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      const condition = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check Missing Field',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            field: 'data.nonExistentField',
            operator: 'equals',
            value: 'something',
          }),
        },
      });

      const noStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Exit',
          position: {x: 200, y: 0},
          config: toPrismaJson({}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: condition.id},
      });

      await prisma.workflowTransition.create({
        data: {
          fromStepId: condition.id,
          toStepId: noStep.id,
          condition: toPrismaJson({branch: 'no'}),
        },
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, condition.id);

      // Verify condition evaluated to 'no' when field doesn't exist
      const conditionExec = await prisma.workflowStepExecution.findFirst({
        where: {executionId: execution.id, stepId: condition.id},
      });

      expect(conditionExec?.status).toBe(StepExecutionStatus.COMPLETED);
      expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('no');
    });
  });

  // ========================================
  // EXIT STEPS
  // ========================================
  describe('Exit Steps', () => {
    it('should complete workflow when EXIT step is reached', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});
      const triggerStep = await prisma.workflowStep.findFirstOrThrow({
        where: {workflowId: workflow.id, type: WorkflowStepType.TRIGGER},
      });

      const exitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Early Exit',
          position: {x: 100, y: 0},
          config: toPrismaJson({reason: 'User already converted'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: exitStep.id},
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson({}),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, exitStep.id);

      // Verify workflow completed
      const completedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(completedExecution?.status).toBe(WorkflowExecutionStatus.COMPLETED);
      expect(completedExecution?.completedAt).toBeDefined();
    });
  });

  describe('Non-Persistent Data in Workflow Context', () => {
    it('should make non-persistent event data available throughout entire workflow execution', async () => {
      const contact = await factories.createContact({
        projectId,
        data: {
          firstName: 'Alice',
          plan: 'enterprise',
        },
      });

      const workflow = await factories.createWorkflow({projectId});

      const triggerStep = await factories.createWorkflowStep({
        workflowId: workflow.id,
        type: WorkflowStepType.TRIGGER,
        name: 'Start',
        position: {x: 0, y: 0},
        config: toPrismaJson({}),
      });

      const exitStep = await factories.createWorkflowStep({
        workflowId: workflow.id,
        type: WorkflowStepType.EXIT,
        name: 'End',
        position: {x: 100, y: 0},
        config: toPrismaJson({}),
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep.id, toStepId: exitStep.id},
      });

      // Create execution with context containing both persistent and non-persistent data
      const contextData = {
        totalSpent: 999.99, // Persistent
        orderId: {value: 'ORD-789', persistent: false}, // Non-persistent
        trackingUrl: {value: 'https://track.example.com/ORD-789', persistent: false}, // Non-persistent
      };

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep.id,
          context: toPrismaJson(contextData),
        },
      });

      // Verify context persists in execution record
      const savedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(savedExecution?.context).toMatchObject({
        totalSpent: 999.99,
        orderId: {value: 'ORD-789', persistent: false},
        trackingUrl: {value: 'https://track.example.com/ORD-789', persistent: false},
      });

      // Process workflow steps
      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep.id);
      await WorkflowExecutionService.processStepExecution(execution.id, exitStep.id);

      // Verify context still available after workflow completes
      const completedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(completedExecution?.status).toBe(WorkflowExecutionStatus.COMPLETED);
      expect(completedExecution?.context).toMatchObject({
        totalSpent: 999.99,
        orderId: {value: 'ORD-789', persistent: false},
        trackingUrl: {value: 'https://track.example.com/ORD-789', persistent: false},
      });

      // Verify contact data was NOT polluted with non-persistent data
      const savedContact = await prisma.contact.findUnique({
        where: {id: contact.id},
      });

      expect(savedContact?.data).toMatchObject({
        firstName: 'Alice',
        plan: 'enterprise',
      });
      expect(savedContact?.data).not.toHaveProperty('orderId');
      expect(savedContact?.data).not.toHaveProperty('trackingUrl');
    });
  });

  // ========================================
  // EVENT DATA IN WORKFLOW CONDITIONS
  // ========================================
  describe('Event Data in Workflow Conditions', () => {
    it('should evaluate conditions using event.* fields', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});

      const triggerStep = await prisma.workflowStep.findFirst({
        where: {workflowId: workflow.id, type: 'TRIGGER'},
      });

      // Create condition step that checks event data
      const conditionStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check if first open',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            field: 'event.isFirstOpen',
            operator: 'equals',
            value: true, // Use boolean, not string
          }),
        },
      });

      const yesStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'First Open',
          position: {x: 200, y: 0},
          config: toPrismaJson({reason: 'first_open'}),
        },
      });

      const noStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Not First Open',
          position: {x: 200, y: 100},
          config: toPrismaJson({reason: 'not_first_open'}),
        },
      });

      // Connect steps
      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
      });
      await prisma.workflowTransition.create({
        data: {fromStepId: conditionStep.id, toStepId: yesStep.id, condition: toPrismaJson({branch: 'yes'})},
      });
      await prisma.workflowTransition.create({
        data: {fromStepId: conditionStep.id, toStepId: noStep.id, condition: toPrismaJson({branch: 'no'})},
      });

      // Create execution with event data
      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep!.id,
          context: toPrismaJson({
            subject: 'Welcome Email',
            from: 'hello@example.com',
            isFirstOpen: true,
            openedAt: new Date().toISOString(),
          }),
        },
      });

      // Process the workflow
      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep!.id);

      // Verify it followed YES branch (first open)
      const completedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
        include: {stepExecutions: true},
      });

      expect(completedExecution?.status).toBe(WorkflowExecutionStatus.COMPLETED);
      expect(completedExecution?.exitReason).toBe('first_open');
    });

    it('should evaluate conditions using event.subject field', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});

      const triggerStep = await prisma.workflowStep.findFirst({
        where: {workflowId: workflow.id, type: 'TRIGGER'},
      });

      const conditionStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check subject',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            field: 'event.subject',
            operator: 'contains',
            value: 'Welcome',
          }),
        },
      });

      const exitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Done',
          position: {x: 200, y: 0},
          config: toPrismaJson({reason: 'matched'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
      });
      await prisma.workflowTransition.create({
        data: {
          fromStepId: conditionStep.id,
          toStepId: exitStep.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep!.id,
          context: toPrismaJson({
            subject: 'Welcome to Plunk!',
            from: 'team@plunk.com',
          }),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep!.id);

      const completedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(completedExecution?.status).toBe(WorkflowExecutionStatus.COMPLETED);
      expect(completedExecution?.exitReason).toBe('matched');
    });

    it('should handle numeric event fields in conditions', async () => {
      const contact = await factories.createContact({projectId});
      const workflow = await factories.createWorkflow({projectId});

      const triggerStep = await prisma.workflowStep.findFirst({
        where: {workflowId: workflow.id, type: 'TRIGGER'},
      });

      const conditionStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.CONDITION,
          name: 'Check opens count',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            field: 'event.opens',
            operator: 'greaterThan',
            value: '3',
          }),
        },
      });

      const exitStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.EXIT,
          name: 'Done',
          position: {x: 200, y: 0},
          config: toPrismaJson({reason: 'engaged'}),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
      });
      await prisma.workflowTransition.create({
        data: {
          fromStepId: conditionStep.id,
          toStepId: exitStep.id,
          condition: toPrismaJson({branch: 'yes'}),
        },
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep!.id,
          context: toPrismaJson({
            subject: 'Newsletter',
            opens: 5,
          }),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep!.id);

      const completedExecution = await prisma.workflowExecution.findUnique({
        where: {id: execution.id},
      });

      expect(completedExecution?.status).toBe(WorkflowExecutionStatus.COMPLETED);
      expect(completedExecution?.exitReason).toBe('engaged');
    });
  });

  // ========================================
  // EVENT DATA IN WEBHOOK CALLS
  // ========================================
  describe('Event Data in Webhook Calls', () => {
    it('should include event data in webhook payload', async () => {
      // Mock fetch to capture webhook calls
      const mockFetch = vi.fn(async () => ({
        ok: true,
        status: 200,
        text: async () => JSON.stringify({success: true}),
      }));
      global.fetch = mockFetch as unknown as typeof fetch;

      const contact = await factories.createContact({
        projectId,
        email: 'test@example.com',
      });
      const workflow = await factories.createWorkflow({projectId});

      const triggerStep = await prisma.workflowStep.findFirst({
        where: {workflowId: workflow.id, type: 'TRIGGER'},
      });

      const webhookStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: WorkflowStepType.WEBHOOK,
          name: 'Send Webhook',
          position: {x: 100, y: 0},
          config: toPrismaJson({
            url: 'https://webhook.example.com/test',
            method: 'POST',
          }),
        },
      });

      await prisma.workflowTransition.create({
        data: {fromStepId: triggerStep!.id, toStepId: webhookStep.id},
      });

      const execution = await prisma.workflowExecution.create({
        data: {
          workflowId: workflow.id,
          contactId: contact.id,
          status: WorkflowExecutionStatus.RUNNING,
          currentStepId: triggerStep!.id,
          context: toPrismaJson({
            subject: 'Welcome Email',
            from: 'hello@example.com',
            messageId: 'msg-123',
            isFirstOpen: true,
            openedAt: '2024-01-15T10:00:00Z',
          }),
        },
      });

      await WorkflowExecutionService.processStepExecution(execution.id, triggerStep!.id);

      // Verify webhook was called with event data
      expect(mockFetch).toHaveBeenCalledWith(
        'https://webhook.example.com/test',
        expect.objectContaining({
          method: 'POST',
          body: expect.stringContaining('"event"'),
        }),
      );

      // Parse the body to verify event data structure
      const callArgs = mockFetch.mock.calls[0];
      const body = JSON.parse(callArgs[1].body);

      expect(body.event).toMatchObject({
        subject: 'Welcome Email',
        from: 'hello@example.com',
        messageId: 'msg-123',
        isFirstOpen: true,
        openedAt: '2024-01-15T10:00:00Z',
      });

      expect(body.contact.email).toBe('test@example.com');
    });
  });
});
