import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
import {
  onShutdown,
  Shutdown,
  SHUTDOWN_DEFAULTS,
} from '../src';
import type { ShutdownOptions } from '../src/types';

describe('Shutdown Comprehensive Tests', () => {
  let shutdownInstance: Shutdown;

  beforeEach(() => {
    // Reset singleton instance for each test
    (Shutdown as any).instance = null;
    // Create a fresh instance for each test
    shutdownInstance = new Shutdown({ autoRegister: false });
  });
  
  afterEach(() => {
    // Clean up singleton
    (Shutdown as any).instance = null;
  });

  describe('Basic Functionality', () => {
    it('should register callbacks', () => {
      const callback = mock(async () => {});
      
      shutdownInstance.onShutdown(callback, 50, 'test-callback');
      
      // We can't directly check callback count, but we can verify it executes
      expect(callback).toBeDefined();
    });

    it('should execute callbacks on shutdown', async () => {
      const callback1 = mock(async () => {});
      const callback2 = mock(async () => {});
      
      shutdownInstance.onShutdown(callback1, 10, 'callback-1');
      shutdownInstance.onShutdown(callback2, 20, 'callback-2');
      
      await shutdownInstance.shutdown();
      
      expect(callback1).toHaveBeenCalledTimes(1);
      expect(callback2).toHaveBeenCalledTimes(1);
    });

    it('should execute callbacks in priority order', async () => {
      const order: string[] = [];
      
      const highPriority = mock(async () => {
        order.push('high');
      });
      const mediumPriority = mock(async () => {
        order.push('medium');
      });
      const lowPriority = mock(async () => {
        order.push('low');
      });
      
      // Lower number = higher priority
      shutdownInstance.onShutdown(lowPriority, 30, 'low');
      shutdownInstance.onShutdown(highPriority, 10, 'high');
      shutdownInstance.onShutdown(mediumPriority, 20, 'medium');
      
      await shutdownInstance.shutdown();
      
      expect(order).toEqual(['high', 'medium', 'low']);
    });

    it('should handle errors in callbacks', async () => {
      const successCallback = mock(async () => {});
      const errorCallback = mock(async () => {
        throw new Error('Callback error');
      });
      
      shutdownInstance.onShutdown(successCallback, 10, 'success');
      shutdownInstance.onShutdown(errorCallback, 20, 'error');
      
      // Should not throw even if callbacks fail
      await expect(shutdownInstance.shutdown()).resolves.toBeUndefined();
      
      expect(successCallback).toHaveBeenCalledTimes(1);
      expect(errorCallback).toHaveBeenCalledTimes(1);
    });

    it('should only shutdown once', async () => {
      const callback = mock(async () => {});
      shutdownInstance.onShutdown(callback);
      
      await shutdownInstance.shutdown();
      await shutdownInstance.shutdown();
      await shutdownInstance.shutdown();
      
      expect(callback).toHaveBeenCalledTimes(1);
    });

    it('should not register callbacks during shutdown', async () => {
      const firstCallback = mock(async () => {
        // Try to register another callback during shutdown
        shutdownInstance.onShutdown(async () => {
          throw new Error('Should not execute');
        });
      });
      
      shutdownInstance.onShutdown(firstCallback);
      
      await shutdownInstance.shutdown();
      
      expect(firstCallback).toHaveBeenCalledTimes(1);
    });
  });

  describe('Timeout Handling', () => {
    it('should timeout if callbacks take too long', async () => {
      const slowShutdown = new Shutdown({ 
        timeout: 100,
        autoRegister: false 
      });
      
      const slowCallback = mock(async () => {
        await new Promise(resolve => setTimeout(resolve, 200));
      });
      
      slowShutdown.onShutdown(slowCallback, 10, 'slow');
      
      const startTime = Date.now();
      await expect(slowShutdown.shutdown()).rejects.toThrow('Shutdown timeout');
      const duration = Date.now() - startTime;
      
      expect(duration).toBeLessThan(150);
    });

    it('should complete if callbacks finish before timeout', async () => {
      const quickShutdown = new Shutdown({ 
        timeout: 1000,
        autoRegister: false 
      });
      
      const quickCallback = mock(async () => {
        await new Promise(resolve => setTimeout(resolve, 10));
      });
      
      quickShutdown.onShutdown(quickCallback);
      
      await expect(quickShutdown.shutdown()).resolves.toBeUndefined();
      expect(quickCallback).toHaveBeenCalledTimes(1);
    });
  });

  describe('Singleton Pattern', () => {
    it('should return same instance via getInstance', () => {
      const instance1 = Shutdown.getInstance();
      const instance2 = Shutdown.getInstance();
      
      expect(instance1).toBe(instance2);
    });

    it('should maintain state across getInstance calls', async () => {
      const instance = Shutdown.getInstance();
      const callback = mock(async () => {});
      
      instance.onShutdown(callback);
      
      const sameInstance = Shutdown.getInstance();
      await sameInstance.shutdown();
      
      expect(callback).toHaveBeenCalledTimes(1);
    });
  });

  describe('Global onShutdown Function', () => {
    it('should use singleton instance', async () => {
      const callback = mock(async () => {});
      
      // Use global function
      onShutdown(callback, 50, 'global-callback');
      
      // Get instance and shutdown
      const instance = Shutdown.getInstance();
      await instance.shutdown();
      
      expect(callback).toHaveBeenCalledTimes(1);
    });

    it('should handle different parameter orders', () => {
      const callback = mock(async () => {});
      
      // Test with all parameters
      onShutdown(callback, 10, 'with-all-params');
      
      // Test with default priority
      onShutdown(callback);
      
      // Test with priority but no name
      onShutdown(callback, 20);
      
      expect(callback).toBeDefined();
    });
  });

  describe('Edge Cases', () => {
    it('should handle synchronous callbacks', async () => {
      const syncCallback = mock(() => {
        // Synchronous callback
      });
      
      shutdownInstance.onShutdown(syncCallback as any, 10, 'sync');
      
      await shutdownInstance.shutdown();
      
      expect(syncCallback).toHaveBeenCalledTimes(1);
    });

    it('should handle callbacks that throw non-Error objects', async () => {
      const throwingCallback = mock(async () => {
        throw 'string error';
      });
      
      shutdownInstance.onShutdown(throwingCallback);
      
      // Should not throw
      await expect(shutdownInstance.shutdown()).resolves.toBeUndefined();
      expect(throwingCallback).toHaveBeenCalledTimes(1);
    });

    it('should handle empty callback list', async () => {
      // No callbacks registered
      await expect(shutdownInstance.shutdown()).resolves.toBeUndefined();
    });

    it('should handle many callbacks', async () => {
      const callbacks = Array.from({ length: 50 }, (_, i) => 
        mock(async () => {})
      );
      
      callbacks.forEach((cb, i) => {
        shutdownInstance.onShutdown(cb, i, `callback-${i}`);
      });
      
      await shutdownInstance.shutdown();
      
      callbacks.forEach(cb => {
        expect(cb).toHaveBeenCalledTimes(1);
      });
    });
  });

  describe('Default Values', () => {
    it('should use default priority', async () => {
      const callback = mock(async () => {});
      
      // No priority specified
      shutdownInstance.onShutdown(callback);
      
      await shutdownInstance.shutdown();
      expect(callback).toHaveBeenCalledTimes(1);
    });

    it('should use default timeout', () => {
      const defaultShutdown = new Shutdown();
      
      // Can't directly test timeout value, but we can verify it doesn't throw
      expect(() => new Shutdown()).not.toThrow();
    });
  });
});