import { tap, expect } from '@git.zone/tstest/tapbundle';
import { 
  delay, 
  retryWithBackoff, 
  withTimeout, 
  parallelLimit,
  debounceAsync,
  AsyncMutex,
  CircuitBreaker
} from '../../../ts/core/utils/async-utils.js';

tap.test('delay should pause execution for specified milliseconds', async () => {
  const startTime = Date.now();
  await delay(100);
  const elapsed = Date.now() - startTime;
  
  // Allow some tolerance for timing
  expect(elapsed).toBeGreaterThan(90);
  expect(elapsed).toBeLessThan(150);
});

tap.test('retryWithBackoff should retry failed operations', async () => {
  let attempts = 0;
  const operation = async () => {
    attempts++;
    if (attempts < 3) {
      throw new Error('Test error');
    }
    return 'success';
  };
  
  const result = await retryWithBackoff(operation, {
    maxAttempts: 3,
    initialDelay: 10
  });
  
  expect(result).toEqual('success');
  expect(attempts).toEqual(3);
});

tap.test('retryWithBackoff should throw after max attempts', async () => {
  let attempts = 0;
  const operation = async () => {
    attempts++;
    throw new Error('Always fails');
  };
  
  let error: Error | null = null;
  try {
    await retryWithBackoff(operation, {
      maxAttempts: 2,
      initialDelay: 10
    });
  } catch (e: any) {
    error = e;
  }
  
  expect(error).not.toBeNull();
  expect(error?.message).toEqual('Always fails');
  expect(attempts).toEqual(2);
});

tap.test('withTimeout should complete operations within timeout', async () => {
  const operation = async () => {
    await delay(50);
    return 'completed';
  };
  
  const result = await withTimeout(operation, 100);
  expect(result).toEqual('completed');
});

tap.test('withTimeout should throw on timeout', async () => {
  const operation = async () => {
    await delay(200);
    return 'never happens';
  };
  
  let error: Error | null = null;
  try {
    await withTimeout(operation, 50);
  } catch (e: any) {
    error = e;
  }
  
  expect(error).not.toBeNull();
  expect(error?.message).toContain('timed out');
});

tap.test('parallelLimit should respect concurrency limit', async () => {
  let concurrent = 0;
  let maxConcurrent = 0;
  
  const items = [1, 2, 3, 4, 5, 6];
  const operation = async (item: number) => {
    concurrent++;
    maxConcurrent = Math.max(maxConcurrent, concurrent);
    await delay(50);
    concurrent--;
    return item * 2;
  };
  
  const results = await parallelLimit(items, operation, 2);
  
  expect(results).toEqual([2, 4, 6, 8, 10, 12]);
  expect(maxConcurrent).toBeLessThan(3);
  expect(maxConcurrent).toBeGreaterThan(0);
});

tap.test('debounceAsync should debounce function calls', async () => {
  let callCount = 0;
  const fn = async (value: string) => {
    callCount++;
    return value;
  };
  
  const debounced = debounceAsync(fn, 50);
  
  // Make multiple calls quickly
  debounced('a');
  debounced('b');
  debounced('c');
  const result = await debounced('d');
  
  // Wait a bit to ensure no more calls
  await delay(100);
  
  expect(result).toEqual('d');
  expect(callCount).toEqual(1); // Only the last call should execute
});

tap.test('AsyncMutex should ensure exclusive access', async () => {
  const mutex = new AsyncMutex();
  const results: number[] = [];
  
  const operation = async (value: number) => {
    await mutex.runExclusive(async () => {
      results.push(value);
      await delay(10);
      results.push(value * 10);
    });
  };
  
  // Run operations concurrently
  await Promise.all([
    operation(1),
    operation(2),
    operation(3)
  ]);
  
  // Results should show sequential execution
  expect(results).toEqual([1, 10, 2, 20, 3, 30]);
});

tap.test('CircuitBreaker should open after failures', async () => {
  const breaker = new CircuitBreaker({
    failureThreshold: 2,
    resetTimeout: 100
  });
  
  let attempt = 0;
  const failingOperation = async () => {
    attempt++;
    throw new Error('Test failure');
  };
  
  // First two failures
  for (let i = 0; i < 2; i++) {
    try {
      await breaker.execute(failingOperation);
    } catch (e) {
      // Expected
    }
  }
  
  expect(breaker.isOpen()).toBeTrue();
  
  // Next attempt should fail immediately
  let error: Error | null = null;
  try {
    await breaker.execute(failingOperation);
  } catch (e: any) {
    error = e;
  }
  
  expect(error?.message).toEqual('Circuit breaker is open');
  expect(attempt).toEqual(2); // Operation not called when circuit is open
  
  // Wait for reset timeout
  await delay(150);
  
  // Circuit should be half-open now, allowing one attempt
  const successOperation = async () => 'success';
  const result = await breaker.execute(successOperation);
  
  expect(result).toEqual('success');
  expect(breaker.getState()).toEqual('closed');
});

tap.start();