import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
import {WorkflowExecutionStatus, WorkflowTriggerType} from '@plunk/db';
import {EventService} from '../EventService';
import {Keys} from '../keys';
import {factories, getPrismaClient} from '../../../../../test/helpers';

// Mock Redis for caching tests - must be inline to avoid hoisting issues
vi.mock('../../database/redis', () => {
  const store = new Map<string, {value: string; expiry?: number}>();
  return {
    redis: {
      get: vi.fn(async (key: string) => {
        const item = store.get(key);
        if (!item) return null;
        if (item.expiry && Date.now() > item.expiry) {
          store.delete(key);
          return null;
        }
        return item.value;
      }),
      set: vi.fn(async (key: string, value: string) => {
        store.set(key, {value});
        return 'OK';
      }),
      setex: vi.fn(async (key: string, seconds: number, value: string) => {
        store.set(key, {value, expiry: Date.now() + seconds * 1000});
        return 'OK';
      }),
      del: vi.fn(async (key: string) => {
        store.delete(key);
        return 1;
      }),
      incr: vi.fn(async (key: string) => {
        const current = store.get(key);
        const newValue = current ? parseInt(current.value) + 1 : 1;
        store.set(key, {value: String(newValue)});
        return newValue;
      }),
      expire: vi.fn(async (key: string, seconds: number) => {
        const item = store.get(key);
        if (!item) return 0;
        store.set(key, {...item, expiry: Date.now() + seconds * 1000});
        return 1;
      }),
      clear: () => store.clear(),
    },
  };
});

describe('EventService', () => {
  let projectId: string;
  const prisma = getPrismaClient();

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

  afterEach(async () => {
    // Clear Redis mock
    const {redis} = await import('../../database/redis');
    if ('clear' in redis) {
      (redis as unknown as {clear: () => void}).clear();
    }
  });

  // ========================================
  // EVENT TRACKING
  // ========================================
  describe('trackEvent', () => {
    it('should create an event record', async () => {
      const contact = await factories.createContact({projectId});

      const event = await EventService.trackEvent(projectId, 'user.signup', contact.id, undefined, {
        source: 'web',
        plan: 'free',
      });

      expect(event.projectId).toBe(projectId);
      expect(event.contactId).toBe(contact.id);
      expect(event.name).toBe('user.signup');
      expect(event.data).toEqual({source: 'web', plan: 'free'});
    });

    it('should track event without contact (project-level event)', async () => {
      const event = await EventService.trackEvent(projectId, 'project.created', undefined, undefined, {
        plan: 'pro',
      });

      expect(event.projectId).toBe(projectId);
      expect(event.contactId).toBeNull();
      expect(event.name).toBe('project.created');
    });

    it('should track event with email reference', async () => {
      const contact = await factories.createContact({projectId});
      const email = await factories.createEmail(projectId, contact.id);

      const event = await EventService.trackEvent(projectId, 'email.opened', contact.id, email.id, {
        userAgent: 'Mozilla/5.0',
      });

      expect(event.emailId).toBe(email.id);
      expect(event.contactId).toBe(contact.id);
    });

    it('should trigger workflows listening for the event', async () => {
      const contact = await factories.createContact({projectId});

      // Create workflow triggered by 'purchase.completed' event
      const workflow = await factories.createWorkflow({
        projectId,
        enabled: true,
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'purchase.completed'},
      });

      // Add a delay step so workflow doesn't complete immediately
      const triggerStep = await prisma.workflowStep.findFirst({
        where: {workflowId: workflow.id, type: 'TRIGGER'},
      });

      const delayStep = await prisma.workflowStep.create({
        data: {
          workflowId: workflow.id,
          type: 'DELAY',
          name: 'Wait',
          position: {x: 100, y: 0},
          config: {amount: 24, unit: 'hours'},
        },
      });

      // Connect steps
      await prisma.workflowTransition.create({
        data: {
          fromStepId: triggerStep!.id,
          toStepId: delayStep.id,
        },
      });

      // Track the event
      await EventService.trackEvent(projectId, 'purchase.completed', contact.id, undefined, {
        amount: 99.99,
        product: 'Premium Plan',
      });

      // Verify workflow execution was created
      const executions = await prisma.workflowExecution.findMany({
        where: {
          workflowId: workflow.id,
          contactId: contact.id,
        },
      });

      expect(executions).toHaveLength(1);
      // Workflow should be in COMPLETED status since DELAY step completes and has no next step
      // (DELAY sets to WAITING then processNextSteps sees no transitions and completes it)
      expect([WorkflowExecutionStatus.WAITING, WorkflowExecutionStatus.COMPLETED]).toContain(executions[0].status);
    });

    it('should NOT trigger disabled workflows', async () => {
      const contact = await factories.createContact({projectId});

      // Create disabled workflow
      const workflow = await factories.createWorkflow({
        projectId,
        enabled: false, // Disabled
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'test.event'},
      });

      await EventService.trackEvent(projectId, 'test.event', contact.id);

      // No execution should be created
      const executions = await prisma.workflowExecution.findMany({
        where: {workflowId: workflow.id},
      });

      expect(executions).toHaveLength(0);
    });

    it('should NOT trigger workflows for different event names', async () => {
      const contact = await factories.createContact({projectId});

      const workflow = await factories.createWorkflow({
        projectId,
        enabled: true,
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'user.signup'},
      });

      // Track different event
      await EventService.trackEvent(projectId, 'user.login', contact.id);

      // No execution should be created
      const executions = await prisma.workflowExecution.findMany({
        where: {workflowId: workflow.id},
      });

      expect(executions).toHaveLength(0);
    });

    it('should respect workflow re-entry settings', async () => {
      const contact = await factories.createContact({projectId});

      const workflow = await factories.createWorkflow({
        projectId,
        enabled: true,
        allowReentry: false, // Do not allow re-entry
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'repeat.event'},
      });

      // First event - should create execution
      await EventService.trackEvent(projectId, 'repeat.event', contact.id);

      let executions = await prisma.workflowExecution.findMany({
        where: {workflowId: workflow.id, contactId: contact.id},
      });

      expect(executions).toHaveLength(1);

      // Second event - should NOT create execution (re-entry not allowed)
      await EventService.trackEvent(projectId, 'repeat.event', contact.id);

      executions = await prisma.workflowExecution.findMany({
        where: {workflowId: workflow.id, contactId: contact.id},
      });

      expect(executions).toHaveLength(1); // Still only 1
    });

    it('should allow re-entry when enabled', async () => {
      const contact = await factories.createContact({projectId});

      const workflow = await factories.createWorkflow({
        projectId,
        enabled: true,
        allowReentry: true, // Allow re-entry
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'repeat.event'},
      });

      // First event
      await EventService.trackEvent(projectId, 'repeat.event', contact.id);

      // Complete first execution
      const firstExecution = await prisma.workflowExecution.findFirst({
        where: {workflowId: workflow.id, contactId: contact.id},
      });

      await prisma.workflowExecution.update({
        where: {id: firstExecution!.id},
        data: {status: WorkflowExecutionStatus.COMPLETED},
      });

      // Second event - should create new execution
      await EventService.trackEvent(projectId, 'repeat.event', contact.id);

      const executions = await prisma.workflowExecution.findMany({
        where: {workflowId: workflow.id, contactId: contact.id},
      });

      expect(executions).toHaveLength(2);
    });

    it('should trigger multiple workflows listening for same event', async () => {
      const contact = await factories.createContact({projectId});

      const workflow1 = await factories.createWorkflow({
        projectId,
        enabled: true,
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'shared.event'},
      });

      const workflow2 = await factories.createWorkflow({
        projectId,
        enabled: true,
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'shared.event'},
      });

      await EventService.trackEvent(projectId, 'shared.event', contact.id);

      const execution1 = await prisma.workflowExecution.findFirst({
        where: {workflowId: workflow1.id},
      });

      const execution2 = await prisma.workflowExecution.findFirst({
        where: {workflowId: workflow2.id},
      });

      expect(execution1).toBeDefined();
      expect(execution2).toBeDefined();
    });
  });

  // ========================================
  // EVENT RETRIEVAL
  // ========================================
  describe('getContactEvents', () => {
    it('should get events for a specific contact', async () => {
      const contact1 = await factories.createContact({projectId});
      const contact2 = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'event.1', contact1.id);
      await EventService.trackEvent(projectId, 'event.2', contact1.id);
      await EventService.trackEvent(projectId, 'event.3', contact2.id);

      const events = await EventService.getContactEvents(projectId, contact1.id);

      expect(events).toHaveLength(2);
      expect(events.every(e => e.contactId === contact1.id)).toBe(true);
    });

    it('should return events in reverse chronological order (newest first)', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'first', contact.id);
      await new Promise(resolve => setTimeout(resolve, 10));
      await EventService.trackEvent(projectId, 'second', contact.id);
      await new Promise(resolve => setTimeout(resolve, 10));
      await EventService.trackEvent(projectId, 'third', contact.id);

      const events = await EventService.getContactEvents(projectId, contact.id);

      expect(events[0].name).toBe('third'); // Newest
      expect(events[1].name).toBe('second');
      expect(events[2].name).toBe('first'); // Oldest
    });

    it('should respect limit parameter', async () => {
      const contact = await factories.createContact({projectId});

      // Create 100 events
      for (let i = 0; i < 100; i++) {
        await EventService.trackEvent(projectId, `event.${i}`, contact.id);
      }

      const events = await EventService.getContactEvents(projectId, contact.id, 25);

      expect(events).toHaveLength(25);
    });

    it('should default to 50 events limit', async () => {
      const contact = await factories.createContact({projectId});

      // Create 60 events
      for (let i = 0; i < 60; i++) {
        await EventService.trackEvent(projectId, `event.${i}`, contact.id);
      }

      const events = await EventService.getContactEvents(projectId, contact.id);

      expect(events).toHaveLength(50); // Default limit
    });
  });

  describe('getProjectEvents', () => {
    it('should get all events for a project', async () => {
      const contact1 = await factories.createContact({projectId});
      const contact2 = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'event.1', contact1.id);
      await EventService.trackEvent(projectId, 'event.2', contact2.id);
      await EventService.trackEvent(projectId, 'event.3');

      const events = await EventService.getProjectEvents(projectId);

      expect(events).toHaveLength(3);
      expect(events.every(e => e.projectId === projectId)).toBe(true);
    });

    it('should filter by event name', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'user.signup', contact.id);
      await EventService.trackEvent(projectId, 'user.login', contact.id);
      await EventService.trackEvent(projectId, 'user.signup', contact.id);

      const events = await EventService.getProjectEvents(projectId, 'user.signup');

      expect(events).toHaveLength(2);
      expect(events.every(e => e.name === 'user.signup')).toBe(true);
    });

    it('should include contact email in results', async () => {
      const contact = await factories.createContact({
        projectId,
        email: 'test@example.com',
      });

      await EventService.trackEvent(projectId, 'test.event', contact.id);

      const events = await EventService.getProjectEvents(projectId);

      expect(events[0].contact?.email).toBe('test@example.com');
    });

    it('should respect limit parameter', async () => {
      const contact = await factories.createContact({projectId});

      for (let i = 0; i < 150; i++) {
        await EventService.trackEvent(projectId, `event.${i}`, contact.id);
      }

      const events = await EventService.getProjectEvents(projectId, undefined, 50);

      expect(events).toHaveLength(50);
    });
  });

  // ========================================
  // EVENT STATISTICS
  // ========================================
  describe('getEventStats', () => {
    it('should return event counts grouped by type', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'user.signup', contact.id);
      await EventService.trackEvent(projectId, 'user.signup', contact.id);
      await EventService.trackEvent(projectId, 'user.login', contact.id);
      await EventService.trackEvent(projectId, 'purchase.completed', contact.id);
      await EventService.trackEvent(projectId, 'purchase.completed', contact.id);
      await EventService.trackEvent(projectId, 'purchase.completed', contact.id);

      const stats = await EventService.getEventStats(projectId);

      expect(stats).toHaveLength(3);

      // Should be ordered by count (desc)
      expect(stats[0].name).toBe('purchase.completed');
      expect(stats[0].count).toBe(3);
      expect(stats[1].name).toBe('user.signup');
      expect(stats[1].count).toBe(2);
      expect(stats[2].name).toBe('user.login');
      expect(stats[2].count).toBe(1);
    });

    it('should filter by date range', async () => {
      const contact = await factories.createContact({projectId});

      const oldDate = new Date('2024-01-01');

      // Create old event directly
      await prisma.event.create({
        data: {
          projectId,
          contactId: contact.id,
          name: 'old.event',
          createdAt: oldDate,
        },
      });

      // Create recent events
      await EventService.trackEvent(projectId, 'recent.event', contact.id);

      const startDate = new Date('2024-05-01');
      const stats = await EventService.getEventStats(projectId, startDate);

      expect(stats).toHaveLength(1);
      expect(stats[0].name).toBe('recent.event');
    });

    it('should handle empty result', async () => {
      const stats = await EventService.getEventStats(projectId);

      expect(stats).toHaveLength(0);
    });
  });

  describe('getUniqueEventNames', () => {
    it('should return unique event names ordered by frequency', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'event.a', contact.id);
      await EventService.trackEvent(projectId, 'event.b', contact.id);
      await EventService.trackEvent(projectId, 'event.b', contact.id);
      await EventService.trackEvent(projectId, 'event.c', contact.id);
      await EventService.trackEvent(projectId, 'event.c', contact.id);
      await EventService.trackEvent(projectId, 'event.c', contact.id);

      const names = await EventService.getUniqueEventNames(projectId);

      expect(names).toHaveLength(3);
      expect(names[0]).toBe('event.c'); // Most frequent
      expect(names[1]).toBe('event.b');
      expect(names[2]).toBe('event.a'); // Least frequent
    });

    it('should return empty array when no events exist', async () => {
      const names = await EventService.getUniqueEventNames(projectId);

      expect(names).toHaveLength(0);
    });
  });

  // ========================================
  // WORKFLOW CACHE MANAGEMENT
  // ========================================
  describe('invalidateWorkflowCache', () => {
    it('should invalidate workflow cache for project', async () => {
      const {redis} = await import('../../database/redis');

      // Set cache
      const cacheKey = Keys.Workflow.enabled(projectId);
      await redis.set(cacheKey, JSON.stringify([{id: 'test'}]));

      // Verify cache exists
      const cached = await redis.get(cacheKey);
      expect(cached).toBeTruthy();

      // Invalidate
      await EventService.invalidateWorkflowCache(projectId);

      // Verify cache deleted
      const afterInvalidation = await redis.get(cacheKey);
      expect(afterInvalidation).toBeNull();
    });

    it('should not throw error if cache does not exist', async () => {
      await expect(EventService.invalidateWorkflowCache(projectId)).resolves.not.toThrow();
    });
  });

  // ========================================
  // EDGE CASES
  // ========================================
  describe('edge cases', () => {
    it('should handle events with complex data structures', async () => {
      const contact = await factories.createContact({projectId});

      const complexData = {
        user: {
          id: 123,
          profile: {
            name: 'John Doe',
            preferences: ['email', 'sms'],
          },
        },
        metadata: {
          source: 'mobile_app',
          version: '2.0.1',
        },
        items: [
          {id: 1, name: 'Item 1', price: 19.99},
          {id: 2, name: 'Item 2', price: 29.99},
        ],
      };

      const event = await EventService.trackEvent(projectId, 'complex.event', contact.id, undefined, complexData);

      expect(event.data).toEqual(complexData);

      // Verify data persists correctly
      const retrieved = await prisma.event.findUnique({
        where: {id: event.id},
      });

      expect(retrieved?.data).toEqual(complexData);
    });

    it('should handle events with null data', async () => {
      const contact = await factories.createContact({projectId});

      const event = await EventService.trackEvent(projectId, 'simple.event', contact.id);

      expect(event.data).toBeNull();
    });

    it('should handle event names with special characters', async () => {
      const contact = await factories.createContact({projectId});

      const eventName = 'user:action@domain.com/path-123';

      const event = await EventService.trackEvent(projectId, eventName, contact.id);

      expect(event.name).toBe(eventName);
    });

    it('should not trigger workflows for events without contact when workflow expects contact', async () => {
      await factories.createWorkflow({
        projectId,
        enabled: true,
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'test.event'},
      });

      // Track event without contact
      await EventService.trackEvent(projectId, 'test.event');

      // No execution should be created (event is not contact-specific)
      const executions = await prisma.workflowExecution.findMany({
        where: {workflow: {projectId}},
      });

      expect(executions).toHaveLength(0);
    });
  });

  // ========================================
  // FIELD DISCOVERY
  // ========================================
  describe('getAvailableEventFields', () => {
    it('should discover fields from event data', async () => {
      const contact = await factories.createContact({projectId});

      // Create events with various fields
      await EventService.trackEvent(projectId, 'email.opened', contact.id, undefined, {
        subject: 'Welcome',
        from: 'hello@example.com',
        openedAt: new Date().toISOString(),
      });

      await EventService.trackEvent(projectId, 'email.opened', contact.id, undefined, {
        subject: 'Newsletter',
        from: 'news@example.com',
        isFirstOpen: true,
        opens: 1,
      });

      const fields = await EventService.getAvailableEventFields(projectId, 'email.opened');

      expect(fields).toContain('event.subject');
      expect(fields).toContain('event.from');
      expect(fields).toContain('event.openedAt');
      expect(fields).toContain('event.isFirstOpen');
      expect(fields).toContain('event.opens');
      expect(fields).toHaveLength(5);
    });

    it('should filter fields by event name', async () => {
      const contact = await factories.createContact({projectId});

      // Create email.opened events
      await EventService.trackEvent(projectId, 'email.opened', contact.id, undefined, {
        subject: 'Test',
        openedAt: new Date().toISOString(),
      });

      // Create email.clicked events
      await EventService.trackEvent(projectId, 'email.clicked', contact.id, undefined, {
        subject: 'Test',
        link: 'https://example.com',
        clickedAt: new Date().toISOString(),
      });

      // Get fields for email.opened only
      const openedFields = await EventService.getAvailableEventFields(projectId, 'email.opened');

      expect(openedFields).toContain('event.subject');
      expect(openedFields).toContain('event.openedAt');
      expect(openedFields).not.toContain('event.link');
      expect(openedFields).not.toContain('event.clickedAt');
    });

    it('should return all event fields when no event name specified', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'email.opened', contact.id, undefined, {
        openedAt: new Date().toISOString(),
      });

      await EventService.trackEvent(projectId, 'email.clicked', contact.id, undefined, {
        link: 'https://example.com',
      });

      const allFields = await EventService.getAvailableEventFields(projectId);

      expect(allFields).toContain('event.openedAt');
      expect(allFields).toContain('event.link');
    });

    it('should return empty array when no events exist', async () => {
      const fields = await EventService.getAvailableEventFields(projectId, 'nonexistent.event');

      expect(fields).toHaveLength(0);
    });

    it('should ignore events with null data', async () => {
      const contact = await factories.createContact({projectId});

      // Event without data
      await EventService.trackEvent(projectId, 'simple.event', contact.id);

      // Event with data
      await EventService.trackEvent(projectId, 'simple.event', contact.id, undefined, {
        field1: 'value1',
      });

      const fields = await EventService.getAvailableEventFields(projectId, 'simple.event');

      expect(fields).toEqual(['event.field1']);
    });

    it('should handle nested field structures', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'complex.event', contact.id, undefined, {
        user: {
          name: 'John',
          email: 'john@example.com',
        },
        metadata: {
          source: 'web',
        },
      });

      const fields = await EventService.getAvailableEventFields(projectId, 'complex.event');

      // Should return top-level keys
      expect(fields).toContain('event.user');
      expect(fields).toContain('event.metadata');
    });

    it('should deduplicate fields across multiple events', async () => {
      const contact = await factories.createContact({projectId});

      // Create 5 events with same fields
      for (let i = 0; i < 5; i++) {
        await EventService.trackEvent(projectId, 'test.event', contact.id, undefined, {
          field1: `value${i}`,
          field2: `value${i}`,
        });
      }

      const fields = await EventService.getAvailableEventFields(projectId, 'test.event');

      expect(fields).toEqual(['event.field1', 'event.field2']);
    });

    it('should respect 100 event sample limit for performance', async () => {
      const contact = await factories.createContact({projectId});

      // Create 150 events with different fields
      for (let i = 0; i < 150; i++) {
        await EventService.trackEvent(projectId, 'test.event', contact.id, undefined, {
          [`field${i}`]: `value${i}`,
        });
      }

      const fields = await EventService.getAvailableEventFields(projectId, 'test.event');

      // Should sample only last 100 events (most recent)
      // So we should see field50-field149 (100 fields)
      expect(fields.length).toBeLessThanOrEqual(100);
    });

    it('should return fields in alphabetical order', async () => {
      const contact = await factories.createContact({projectId});

      await EventService.trackEvent(projectId, 'test.event', contact.id, undefined, {
        zebra: 'last',
        apple: 'first',
        middle: 'middle',
      });

      const fields = await EventService.getAvailableEventFields(projectId, 'test.event');

      expect(fields).toEqual(['event.apple', 'event.middle', 'event.zebra']);
    });

    it('should handle email event data structure', async () => {
      const contact = await factories.createContact({projectId});
      const email = await factories.createEmail(projectId, contact.id);

      // Simulate actual email.sent event data
      await EventService.trackEvent(projectId, 'email.sent', contact.id, email.id, {
        subject: 'Welcome Email',
        from: 'hello@example.com',
        fromName: 'Example Team',
        messageId: 'msg-123',
        templateId: 'tpl-456',
        campaignId: null,
        sourceType: 'TRANSACTIONAL',
        sentAt: new Date().toISOString(),
      });

      const fields = await EventService.getAvailableEventFields(projectId, 'email.sent');

      expect(fields).toContain('event.subject');
      expect(fields).toContain('event.from');
      expect(fields).toContain('event.fromName');
      expect(fields).toContain('event.messageId');
      expect(fields).toContain('event.templateId');
      expect(fields).toContain('event.campaignId');
      expect(fields).toContain('event.sourceType');
      expect(fields).toContain('event.sentAt');
    });
  });

  describe('Event Data - Persistent vs Non-Persistent', () => {
    it('should store all event data (persistent + non-persistent) in event record', async () => {
      const contact = await factories.createContact({projectId});

      const eventData = {
        totalSpent: 599.99, // Persistent
        orderId: {value: 'ORD-123', persistent: false}, // Non-persistent
        items: {value: [{name: 'Widget', qty: 2}], persistent: false}, // Non-persistent
      };

      const event = await EventService.trackEvent(projectId, 'purchase', contact.id, undefined, eventData);

      // Event should store ALL data for workflow access
      expect(event.data).toMatchObject({
        totalSpent: 599.99,
        orderId: {value: 'ORD-123', persistent: false},
        items: {value: [{name: 'Widget', qty: 2}], persistent: false},
      });
    });

    it('should pass all event data to workflow execution context', async () => {
      const contact = await factories.createContact({projectId});

      const workflow = await factories.createWorkflow({
        projectId,
        enabled: true,
        triggerType: WorkflowTriggerType.EVENT,
        triggerConfig: {eventName: 'order_placed'},
      });

      await factories.createWorkflowStep({
        workflowId: workflow.id,
        type: 'TRIGGER',
        name: 'Order Trigger',
        position: {x: 0, y: 0},
        config: {},
      });

      const eventData = {
        amount: 99.99, // Persistent
        confirmationCode: {value: 'CONF-456', persistent: false}, // Non-persistent
      };

      await EventService.trackEvent(projectId, 'order_placed', contact.id, undefined, eventData);

      // Wait for async workflow creation
      await new Promise(resolve => setTimeout(resolve, 50));

      const execution = await prisma.workflowExecution.findFirst({
        where: {
          workflowId: workflow.id,
          contactId: contact.id,
        },
      });

      // Execution context should have ALL event data
      expect(execution).toBeDefined();
      expect(execution?.context).toMatchObject({
        amount: 99.99,
        confirmationCode: {value: 'CONF-456', persistent: false},
      });
    });
  });

  // ========================================
  // RESERVED EVENT VALIDATION
  // ========================================
  describe('isReservedEvent', () => {
    describe('Email events (email.*)', () => {
      it('should identify email.sent as reserved', () => {
        expect(EventService.isReservedEvent('email.sent')).toBe(true);
      });

      it('should identify email.delivery as reserved', () => {
        expect(EventService.isReservedEvent('email.delivery')).toBe(true);
      });

      it('should identify email.open as reserved', () => {
        expect(EventService.isReservedEvent('email.open')).toBe(true);
      });

      it('should identify email.click as reserved', () => {
        expect(EventService.isReservedEvent('email.click')).toBe(true);
      });

      it('should identify email.bounce as reserved', () => {
        expect(EventService.isReservedEvent('email.bounce')).toBe(true);
      });

      it('should identify email.complaint as reserved', () => {
        expect(EventService.isReservedEvent('email.complaint')).toBe(true);
      });

      it('should identify any email.* pattern as reserved', () => {
        expect(EventService.isReservedEvent('email.custom')).toBe(true);
        expect(EventService.isReservedEvent('email.anything')).toBe(true);
      });
    });

    describe('Contact events', () => {
      it('should identify contact.subscribed as reserved', () => {
        expect(EventService.isReservedEvent('contact.subscribed')).toBe(true);
      });

      it('should identify contact.unsubscribed as reserved', () => {
        expect(EventService.isReservedEvent('contact.unsubscribed')).toBe(true);
      });

      it('should not identify other contact.* events as reserved', () => {
        expect(EventService.isReservedEvent('contact.created')).toBe(false);
        expect(EventService.isReservedEvent('contact.updated')).toBe(false);
      });
    });

    describe('Segment events (segment.*.entry, segment.*.exit)', () => {
      it('should identify segment.*.entry as reserved', () => {
        expect(EventService.isReservedEvent('segment.vip-users.entry')).toBe(true);
        expect(EventService.isReservedEvent('segment.premium.entry')).toBe(true);
        expect(EventService.isReservedEvent('segment.active-subscribers.entry')).toBe(true);
      });

      it('should identify segment.*.exit as reserved', () => {
        expect(EventService.isReservedEvent('segment.vip-users.exit')).toBe(true);
        expect(EventService.isReservedEvent('segment.premium.exit')).toBe(true);
        expect(EventService.isReservedEvent('segment.active-subscribers.exit')).toBe(true);
      });

      it('should not identify other segment.* events as reserved', () => {
        expect(EventService.isReservedEvent('segment.created')).toBe(false);
        expect(EventService.isReservedEvent('segment.vip-users.updated')).toBe(false);
        expect(EventService.isReservedEvent('segment.premium')).toBe(false);
      });
    });

    describe('Non-reserved events', () => {
      it('should not identify custom user events as reserved', () => {
        expect(EventService.isReservedEvent('user.signup')).toBe(false);
        expect(EventService.isReservedEvent('purchase.completed')).toBe(false);
        expect(EventService.isReservedEvent('order.placed')).toBe(false);
        expect(EventService.isReservedEvent('custom.event')).toBe(false);
      });

      it('should not identify events with similar prefixes as reserved', () => {
        expect(EventService.isReservedEvent('emails.sent')).toBe(false);
        expect(EventService.isReservedEvent('contacts.subscribed')).toBe(false);
        expect(EventService.isReservedEvent('segments.entry')).toBe(false);
      });
    });
  });
});
