import type { AwilixContainer } from 'awilix';
import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { ServiceLifecycleManager } from '../src/utils/lifecycle';

describe('ServiceLifecycleManager', () => {
  let manager: ServiceLifecycleManager;

  beforeEach(() => {
    manager = new ServiceLifecycleManager();
  });

  describe('initializeServices', () => {
    it('should initialize services with connect method', async () => {
      const mockCache = {
        connect: mock(() => Promise.resolve()),
      };

      const mockMongoClient = {
        connect: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          cache: mockCache,
          mongoClient: mockMongoClient,
          postgresClient: null, // Not configured
        },
      } as unknown as AwilixContainer;

      await manager.initializeServices(mockContainer);

      expect(mockCache.connect).toHaveBeenCalled();
      expect(mockMongoClient.connect).toHaveBeenCalled();
    });

    it('should initialize services with initialize method', async () => {
      const mockService = {
        initialize: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          cache: mockService,
        },
      } as unknown as AwilixContainer;

      await manager.initializeServices(mockContainer);

      expect(mockService.initialize).toHaveBeenCalled();
    });

    it('should handle initialization errors', async () => {
      const mockService = {
        connect: mock(() => Promise.reject(new Error('Connection failed'))),
      };

      const mockContainer = {
        cradle: {
          cache: mockService,
        },
      } as unknown as AwilixContainer;

      await expect(manager.initializeServices(mockContainer)).rejects.toThrow('Connection failed');
    });

    it('should handle initialization timeout', async () => {
      const mockService = {
        connect: mock(() => new Promise(() => {})), // Never resolves
      };

      const mockContainer = {
        cradle: {
          cache: mockService,
        },
      } as unknown as AwilixContainer;

      await expect(manager.initializeServices(mockContainer, 100)).rejects.toThrow(
        'cache initialization timed out after 100ms'
      );
    });
  });

  describe('shutdownServices', () => {
    it('should shutdown services with disconnect method', async () => {
      const mockCache = {
        disconnect: mock(() => Promise.resolve()),
      };

      const mockMongoClient = {
        disconnect: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          cache: mockCache,
          mongoClient: mockMongoClient,
        },
      } as unknown as AwilixContainer;

      await manager.shutdownServices(mockContainer);

      expect(mockCache.disconnect).toHaveBeenCalled();
      expect(mockMongoClient.disconnect).toHaveBeenCalled();
    });

    it('should shutdown services with close method', async () => {
      const mockService = {
        close: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          queueManager: mockService,
        },
      } as unknown as AwilixContainer;

      await manager.shutdownServices(mockContainer);

      expect(mockService.close).toHaveBeenCalled();
    });

    it('should shutdown services with shutdown method', async () => {
      const mockService = {
        shutdown: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          cache: mockService,
        },
      } as unknown as AwilixContainer;

      await manager.shutdownServices(mockContainer);

      expect(mockService.shutdown).toHaveBeenCalled();
    });

    it('should handle shutdown errors gracefully', async () => {
      const mockService = {
        disconnect: mock(() => Promise.reject(new Error('Disconnect failed'))),
      };

      const mockContainer = {
        cradle: {
          cache: mockService,
        },
      } as unknown as AwilixContainer;

      // Should not throw
      await manager.shutdownServices(mockContainer);
    });

    it('should shutdown services in reverse order', async () => {
      const callOrder: string[] = [];

      const mockCache = {
        disconnect: mock(() => {
          callOrder.push('cache');
          return Promise.resolve();
        }),
      };

      const mockQueueManager = {
        close: mock(() => {
          callOrder.push('queue');
          return Promise.resolve();
        }),
      };

      const mockContainer = {
        cradle: {
          cache: mockCache,
          queueManager: mockQueueManager,
        },
      } as unknown as AwilixContainer;

      await manager.shutdownServices(mockContainer);

      // Queue manager should be shutdown before cache (reverse order)
      expect(callOrder[0]).toBe('queue');
      expect(callOrder[1]).toBe('cache');
    });
  });

  describe('mixed lifecycle methods', () => {
    it('should handle services with multiple lifecycle methods', async () => {
      const mockService = {
        connect: mock(() => Promise.resolve()),
        disconnect: mock(() => Promise.resolve()),
        initialize: mock(() => Promise.resolve()),
        shutdown: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          cache: mockService,
        },
      } as unknown as AwilixContainer;

      // Initialize should prefer connect over initialize
      await manager.initializeServices(mockContainer);
      expect(mockService.connect).toHaveBeenCalled();
      expect(mockService.initialize).not.toHaveBeenCalled();

      // Shutdown should prefer disconnect over others
      await manager.shutdownServices(mockContainer);
      expect(mockService.disconnect).toHaveBeenCalled();
      expect(mockService.shutdown).not.toHaveBeenCalled();
    });
  });

  describe('complete lifecycle flow', () => {
    it('should handle full initialization and shutdown cycle', async () => {
      const mockCache = {
        connect: mock(() => Promise.resolve()),
        disconnect: mock(() => Promise.resolve()),
      };

      const mockMongoClient = {
        connect: mock(() => Promise.resolve()),
        disconnect: mock(() => Promise.resolve()),
      };

      const mockPostgresClient = {
        connect: mock(() => Promise.resolve()),
        close: mock(() => Promise.resolve()),
      };

      const mockQuestdbClient = {
        initialize: mock(() => Promise.resolve()),
        shutdown: mock(() => Promise.resolve()),
      };

      const mockContainer = {
        cradle: {
          cache: mockCache,
          mongoClient: mockMongoClient,
          postgresClient: mockPostgresClient,
          questdbClient: mockQuestdbClient,
          proxyManager: null, // Not configured
          queueManager: null, // Not configured
        },
      } as unknown as AwilixContainer;

      // Initialize all services
      await manager.initializeServices(mockContainer);

      expect(mockCache.connect).toHaveBeenCalled();
      expect(mockMongoClient.connect).toHaveBeenCalled();
      expect(mockPostgresClient.connect).toHaveBeenCalled();
      expect(mockQuestdbClient.initialize).toHaveBeenCalled();

      // Shutdown all services
      await manager.shutdownServices(mockContainer);

      expect(mockCache.disconnect).toHaveBeenCalled();
      expect(mockMongoClient.disconnect).toHaveBeenCalled();
      expect(mockPostgresClient.close).toHaveBeenCalled();
      expect(mockQuestdbClient.shutdown).toHaveBeenCalled();
    });
  });
});
