import { asClass, asFunction, asValue, createContainer } from 'awilix';
import { describe, expect, it, mock } from 'bun:test';
import {
  registerApplicationServices,
  registerCacheServices,
  registerDatabaseServices,
} from '../src/registrations';

// Mock the queue module
mock.module('@stock-bot/queue', () => ({
  createServiceCache: mock(() => ({
    get: mock(() => Promise.resolve(null)),
    set: mock(() => Promise.resolve()),
    del: mock(() => Promise.resolve()),
  })),
}));

describe('DI Registrations', () => {
  describe('registerCacheServices', () => {
    it('should register null cache when redis disabled', () => {
      const container = createContainer();
      const config = {
        service: {
          name: 'test-service',
          type: 'WORKER' as const,
        },
        redis: {
          enabled: false,
          host: 'localhost',
          port: 6379,
        },
      } as any;

      registerCacheServices(container, config);

      const cache = container.resolve('cache');
      expect(cache).toBeNull();
    });

    it('should register redis cache when redis config exists', () => {
      const container = createContainer();

      // Register logger first as it's a dependency
      container.register({
        logger: asValue({
          info: () => {},
          error: () => {},
          warn: () => {},
          debug: () => {},
        }),
      });

      const config = {
        service: {
          name: 'test-service',
          type: 'WORKER' as const,
        },
        redis: {
          enabled: true,
          host: 'localhost',
          port: 6379,
          db: 1,
        },
      } as any;

      registerCacheServices(container, config);

      const cache = container.resolve('cache');
      expect(cache).toBeDefined();
    });

    it('should register both cache and globalCache', () => {
      const container = createContainer();

      // Register logger dependency
      container.register({
        logger: asValue({
          info: () => {},
          error: () => {},
          warn: () => {},
          debug: () => {},
        }),
      });

      const config = {
        service: {
          name: 'test-service',
          serviceName: 'test-service',
        },
        redis: {
          enabled: true,
          host: 'localhost',
          port: 6379,
          db: 1,
        },
      } as any;

      registerCacheServices(container, config);

      const cache = container.resolve('cache');
      const globalCache = container.resolve('globalCache');
      expect(cache).toBeDefined();
      expect(globalCache).toBeDefined();
    });
  });

  describe('registerDatabaseServices', () => {
    it('should register MongoDB when config exists', () => {
      const container = createContainer();

      // Mock MongoDB client
      const mockMongoClient = {
        connect: mock(() => Promise.resolve()),
        disconnect: mock(() => Promise.resolve()),
        getDb: mock(() => ({})),
      };

      // Mock the MongoDB factory
      mock.module('@stock-bot/mongodb', () => ({
        MongoDBClient: class {
          constructor() {
            return mockMongoClient;
          }
        },
      }));

      const config = {
        mongodb: {
          enabled: true,
          uri: 'mongodb://localhost',
          database: 'test-db',
        },
      } as any;

      registerDatabaseServices(container, config);

      expect(container.hasRegistration('mongoClient')).toBe(true);
    });

    it('should register PostgreSQL when config exists', () => {
      const container = createContainer();

      // Mock Postgres client
      const mockPostgresClient = {
        connect: mock(() => Promise.resolve()),
        disconnect: mock(() => Promise.resolve()),
        query: mock(() => Promise.resolve({ rows: [] })),
      };

      // Mock the Postgres factory
      mock.module('@stock-bot/postgres', () => ({
        PostgresClient: class {
          constructor() {
            return mockPostgresClient;
          }
        },
      }));

      const config = {
        postgres: {
          enabled: true,
          host: 'localhost',
          port: 5432,
          user: 'user',
          password: 'pass',
          database: 'test-db',
        },
      } as any;

      registerDatabaseServices(container, config);

      expect(container.hasRegistration('postgresClient')).toBe(true);
    });

    it('should register QuestDB when config exists', () => {
      const container = createContainer();

      // Mock QuestDB client
      const mockQuestdbClient = {
        connect: mock(() => Promise.resolve()),
        disconnect: mock(() => Promise.resolve()),
        query: mock(() => Promise.resolve({ data: [] })),
      };

      // Mock the QuestDB factory
      mock.module('@stock-bot/questdb', () => ({
        QuestDBClient: class {
          constructor() {
            return mockQuestdbClient;
          }
        },
      }));

      const config = {
        questdb: {
          enabled: true,
          host: 'localhost',
          httpPort: 9000,
          pgPort: 8812,
          influxPort: 9009,
          database: 'questdb',
        },
      } as any;

      registerDatabaseServices(container, config);

      expect(container.hasRegistration('questdbClient')).toBe(true);
    });

    it('should not register disabled databases', () => {
      const container = createContainer();

      const config = {
        mongodb: { enabled: false },
        postgres: { enabled: false },
        questdb: undefined,
      } as any;

      registerDatabaseServices(container, config);

      // Services are registered but with null values when disabled
      expect(container.hasRegistration('mongoClient')).toBe(true);
      expect(container.hasRegistration('postgresClient')).toBe(true);
      expect(container.hasRegistration('questdbClient')).toBe(true);

      // Verify they resolve to null
      expect(container.resolve('mongoClient')).toBeNull();
      expect(container.resolve('postgresClient')).toBeNull();
      expect(container.resolve('questdbClient')).toBeNull();
    });
  });

  describe('registerApplicationServices', () => {
    it('should register browser when config exists', () => {
      const container = createContainer();

      // Mock browser factory
      const mockBrowser = {
        launch: mock(() => Promise.resolve()),
        close: mock(() => Promise.resolve()),
      };

      mock.module('@stock-bot/browser', () => ({
        createBrowser: () => mockBrowser,
      }));

      const config = {
        browser: {
          headless: true,
          timeout: 30000,
        },
      } as any;

      registerApplicationServices(container, config);

      expect(container.hasRegistration('browser')).toBe(true);
    });

    it('should register proxy when config exists', () => {
      const container = createContainer();

      // Mock proxy factory
      const mockProxy = {
        getProxy: mock(() => 'http://proxy:8080'),
      };

      mock.module('@stock-bot/proxy', () => ({
        createProxyManager: () => mockProxy,
      }));

      const config = {
        proxy: {
          enabled: true,
          url: 'http://proxy:8080',
        },
      } as any;

      registerApplicationServices(container, config);

      expect(container.hasRegistration('proxyManager')).toBe(true);
    });

    it('should register queue manager when queue config exists', () => {
      const container = createContainer();

      // Mock dependencies
      container.register({
        cache: asValue({
          get: mock(() => Promise.resolve(null)),
          set: mock(() => Promise.resolve()),
        }),
        handlerRegistry: asValue({
          getHandler: mock(() => null),
          getAllHandlers: mock(() => []),
        }),
        logger: asValue({
          info: mock(() => {}),
          error: mock(() => {}),
          warn: mock(() => {}),
          debug: mock(() => {}),
        }),
      });

      // Mock queue manager
      const mockQueueManager = {
        getQueue: mock(() => ({})),
        startAllWorkers: mock(() => {}),
        shutdown: mock(() => Promise.resolve()),
      };

      mock.module('@stock-bot/queue', () => ({
        QueueManager: class {
          constructor() {
            return mockQueueManager;
          }
        },
      }));

      const config = {
        service: {
          name: 'test-service',
          serviceName: 'test-service',
        },
        queue: {
          enabled: true,
          workers: 2,
          concurrency: 5,
        },
      } as any;

      registerApplicationServices(container, config);

      expect(container.hasRegistration('queueManager')).toBe(true);
    });

    it('should not register services when configs are missing', () => {
      const container = createContainer();

      const config = {} as any;

      registerApplicationServices(container, config);

      expect(container.hasRegistration('browser')).toBe(true);
      expect(container.hasRegistration('proxyManager')).toBe(true);
      expect(container.hasRegistration('queueManager')).toBe(true);

      // They should be registered as null
      const browser = container.resolve('browser');
      const proxyManager = container.resolve('proxyManager');
      const queueManager = container.resolve('queueManager');

      expect(browser).toBe(null);
      expect(proxyManager).toBe(null);
      expect(queueManager).toBe(null);
    });
  });

  describe('dependency resolution', () => {
    it('should properly resolve cache dependencies', () => {
      const container = createContainer();

      const config = {
        service: {
          name: 'test-service',
          serviceName: 'test-service',
        },
        redis: {
          enabled: true,
          host: 'localhost',
          port: 6379,
          db: 0,
        },
      } as any;

      registerCacheServices(container, config);

      // Should have registered cache
      expect(container.hasRegistration('cache')).toBe(true);
      expect(container.hasRegistration('globalCache')).toBe(true);
    });

    it('should handle circular dependencies gracefully', () => {
      const container = createContainer();

      // Register services with potential circular deps
      container.register({
        serviceA: asFunction(({ serviceB }) => ({ b: serviceB })).singleton(),
        serviceB: asFunction(({ serviceA }) => ({ a: serviceA })).singleton(),
      });

      // This should throw or handle gracefully
      expect(() => container.resolve('serviceA')).toThrow();
    });
  });

  describe('registration options', () => {
    it('should register services as singletons', () => {
      const container = createContainer();

      const config = {
        browser: {
          headless: true,
          timeout: 30000,
        },
      } as any;

      registerApplicationServices(container, config);

      // Check that browser was registered as singleton
      const registration = container.getRegistration('browser');
      expect(registration).toBeDefined();
      expect(registration?.lifetime).toBe('SINGLETON');
    });
  });
});
