import { vi } from 'vitest';
import { Queue, Worker, Job } from 'bullmq';

/**
 * Job testing helper
 * Provides utilities to test BullMQ job processors without Redis
 */

export interface MockJob<T = any> {
  id: string;
  data: T;
  attemptsMade: number;
  processedOn?: number;
  finishedOn?: number;
  returnvalue?: any;
  failedReason?: string;
}

/**
 * Create a mock BullMQ job
 */
export function createMockJob<T>(data: T, options: Partial<MockJob<T>> = {}): Job<T> {
  const mockJob = {
    id: options.id || `job-${Date.now()}`,
    data,
    attemptsMade: options.attemptsMade || 0,
    processedOn: options.processedOn,
    finishedOn: options.finishedOn,
    returnvalue: options.returnvalue,
    failedReason: options.failedReason,
    // Mock methods
    updateProgress: vi.fn(),
    log: vi.fn(),
    moveToCompleted: vi.fn(),
    moveToFailed: vi.fn(),
    remove: vi.fn(),
    retry: vi.fn(),
  } as unknown as Job<T>;

  return mockJob;
}

/**
 * Mock queue for testing job creation
 */
export class MockQueue<T = any> {
  private jobs: Array<{ name?: string; data: T; opts?: any }> = [];

  async add(name: string | T, data?: T | any, opts?: any) {
    // Handle both queue.add(data) and queue.add(name, data) patterns
    const jobName = typeof name === 'string' ? name : undefined;
    const jobData = typeof name === 'string' ? data : name;
    const jobOpts = typeof name === 'string' ? opts : data;

    this.jobs.push({
      name: jobName,
      data: jobData,
      opts: jobOpts,
    });

    return createMockJob(jobData, { id: `mock-${this.jobs.length}` });
  }

  getJobs() {
    return this.jobs;
  }

  getJobsByName(name: string) {
    return this.jobs.filter((job) => job.name === name);
  }

  clear() {
    this.jobs = [];
  }

  async close() {
    // No-op for mock
  }
}

/**
 * Create mock queues for testing
 */
export function createMockQueues() {
  return {
    email: new MockQueue(),
    campaign: new MockQueue(),
    scheduledCampaign: new MockQueue(),
    workflow: new MockQueue(),
    import: new MockQueue(),
    segmentCount: new MockQueue(),
    domainVerification: new MockQueue(),
  };
}

/**
 * Job processor test helper
 * Allows testing job processors in isolation
 */
export class JobProcessorTester<T = any> {
  private processor: (job: Job<T>) => Promise<any>;

  constructor(processor: (job: Job<T>) => Promise<any>) {
    this.processor = processor;
  }

  /**
   * Process a job with test data
   */
  async process(data: T, options: Partial<MockJob<T>> = {}) {
    const job = createMockJob(data, options);
    return this.processor(job);
  }

  /**
   * Process multiple jobs
   */
  async processMany(dataArray: T[], options: Partial<MockJob<T>> = {}) {
    const results = [];
    for (const data of dataArray) {
      const result = await this.process(data, options);
      results.push(result);
    }
    return results;
  }
}

/**
 * Create a job processor tester
 */
export function createJobTester<T = any>(processor: (job: Job<T>) => Promise<any>) {
  return new JobProcessorTester<T>(processor);
}

/**
 * Mock external services for job testing
 */
export function createServiceMocks() {
  return {
    // Mock SES (email sending)
    ses: {
      sendEmail: vi.fn().mockResolvedValue({ MessageId: 'mock-message-id' }),
      sendRawEmail: vi.fn().mockResolvedValue({ MessageId: 'mock-message-id' }),
    },

    // Mock S3 (file storage)
    s3: {
      upload: vi.fn().mockResolvedValue({ Location: 'https://mock-s3.com/file.jpg' }),
      getSignedUrl: vi.fn().mockResolvedValue('https://mock-s3.com/signed-url'),
      deleteObject: vi.fn().mockResolvedValue({}),
    },

    // Mock Stripe (billing)
    stripe: {
      customers: {
        create: vi.fn().mockResolvedValue({ id: 'cus_mock' }),
        retrieve: vi.fn().mockResolvedValue({ id: 'cus_mock' }),
      },
      subscriptions: {
        create: vi.fn().mockResolvedValue({ id: 'sub_mock' }),
        update: vi.fn().mockResolvedValue({ id: 'sub_mock' }),
      },
    },

    // Mock Redis
    redis: {
      get: vi.fn().mockResolvedValue(null),
      set: vi.fn().mockResolvedValue('OK'),
      del: vi.fn().mockResolvedValue(1),
      incr: vi.fn().mockResolvedValue(1),
      expire: vi.fn().mockResolvedValue(1),
    },
  };
}

/**
 * Wait for jobs to complete (useful in integration tests with real queues)
 */
export async function waitForJobs(queue: Queue, timeout = 5000): Promise<void> {
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    const counts = await queue.getJobCounts('waiting', 'active', 'delayed');
    const pending = counts.waiting + counts.active + counts.delayed;

    if (pending === 0) {
      return;
    }

    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  throw new Error(`Jobs did not complete within ${timeout}ms`);
}
