import { asClass, asFunction, asValue, createContainer, InjectionMode } from 'awilix';
import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { ServiceContainerBuilder } from '../src/container/builder';
import { OperationContext } from '../src/operation-context';
import { PoolSizeCalculator } from '../src/pool-size-calculator';
import { HandlerScanner } from '../src/scanner/handler-scanner';
import { ServiceApplication } from '../src/service-application';

describe('Dependency Injection', () => {
  describe('ServiceContainerBuilder', () => {
    let builder: ServiceContainerBuilder;

    beforeEach(() => {
      builder = new ServiceContainerBuilder();
    });

    it('should create container with default configuration', async () => {
      const config = {
        name: 'test-service',
        version: '1.0.0',
        service: {
          name: 'test-service',
          type: 'WORKER' as const,
          serviceName: 'test-service',
          port: 3000,
        },
        log: {
          level: 'info',
          format: 'json',
        },
      };

      builder.withConfig(config);
      builder.skipInitialization(); // Skip initialization for testing

      const container = await builder.build();
      expect(container).toBeDefined();
    });

    it('should configure services', async () => {
      const config = {
        name: 'test-service',
        version: '1.0.0',
        service: {
          name: 'test-service',
          type: 'WORKER' as const,
          serviceName: 'test-service',
          port: 3000,
        },
        log: {
          level: 'info',
          format: 'json',
        },
      };

      builder
        .withConfig(config)
        .withOptions({
          enableCache: true,
          enableQueue: false,
        })
        .skipInitialization();

      const container = await builder.build();
      expect(container).toBeDefined();
    });
  });

  describe('Basic Container Operations', () => {
    it('should register and resolve values', () => {
      const container = createContainer({
        injectionMode: InjectionMode.PROXY,
      });

      container.register({
        testValue: asValue('test'),
      });

      expect(container.resolve('testValue')).toBe('test');
    });

    it('should register and resolve classes', () => {
      class TestClass {
        getValue() {
          return 'test';
        }
      }

      const container = createContainer({
        injectionMode: InjectionMode.PROXY,
      });

      container.register({
        testClass: asClass(TestClass),
      });

      const instance = container.resolve('testClass');
      expect(instance).toBeInstanceOf(TestClass);
      expect(instance.getValue()).toBe('test');
    });

    it('should handle dependencies', () => {
      const container = createContainer({
        injectionMode: InjectionMode.PROXY,
      });

      // Test with scoped container
      container.register({
        config: asValue({ host: 'localhost', port: 5432 }),
        connection: asFunction(() => {
          const config = container.resolve('config');
          return `postgresql://${config.host}:${config.port}/mydb`;
        }).scoped(),
      });

      const connection = container.resolve('connection');
      expect(connection).toBe('postgresql://localhost:5432/mydb');
    });
  });

  describe('OperationContext', () => {
    it('should create operation context', () => {
      const context = new OperationContext({
        handlerName: 'test-handler',
        operationName: 'test-op',
      });

      expect(context.traceId).toBeDefined();
      expect(context.logger).toBeDefined();
      expect(context.metadata).toEqual({});
    });

    it('should include metadata', () => {
      const metadata = { userId: '123', source: 'api' };
      const context = new OperationContext({
        handlerName: 'test-handler',
        operationName: 'test-op',
        metadata,
      });

      expect(context.metadata).toEqual(metadata);
    });

    it('should track execution time', async () => {
      const context = new OperationContext({
        handlerName: 'test-handler',
        operationName: 'test-op',
      });

      await new Promise(resolve => setTimeout(resolve, 10));

      const executionTime = context.getExecutionTime();
      expect(executionTime).toBeGreaterThanOrEqual(10);
    });

    it('should create child context', () => {
      const parentContext = new OperationContext({
        handlerName: 'parent-handler',
        operationName: 'parent-op',
        metadata: { parentId: '123' },
      });

      const childContext = parentContext.createChild('child-op', { childId: '456' });

      expect(childContext.traceId).toBe(parentContext.traceId);
      expect(childContext.metadata).toEqual({ parentId: '123', childId: '456' });
    });
  });

  describe('HandlerScanner', () => {
    it('should create scanner instance', () => {
      const mockRegistry = {
        register: mock(() => {}),
        getHandlers: mock(() => []),
      };

      const mockContainer = createContainer({
        injectionMode: InjectionMode.PROXY,
      });

      const scanner = new HandlerScanner(mockRegistry as any, mockContainer);

      expect(scanner).toBeDefined();
      expect(scanner.scanHandlers).toBeDefined();
    });
  });

  describe('ServiceApplication', () => {
    it('should create service application', () => {
      const mockConfig = {
        name: 'test-service',
        version: '1.0.0',
        service: {
          name: 'test-service',
          type: 'WORKER' as const,
          serviceName: 'test-service',
          port: 3000,
        },
        log: {
          level: 'info',
          format: 'json',
        },
      };

      const serviceConfig = {
        serviceName: 'test-service',
      };

      const app = new ServiceApplication(mockConfig, serviceConfig);

      expect(app).toBeDefined();
      expect(app.start).toBeDefined();
      expect(app.stop).toBeDefined();
    });
  });

  describe('Pool Size Calculator', () => {
    it('should calculate pool size for services', () => {
      const recommendation = PoolSizeCalculator.calculate('web-api');

      expect(recommendation.min).toBe(2);
      expect(recommendation.max).toBe(10);
      expect(recommendation.idle).toBe(2);
    });

    it('should calculate pool size for handlers', () => {
      const recommendation = PoolSizeCalculator.calculate('data-ingestion', 'batch-import');

      expect(recommendation.min).toBe(10);
      expect(recommendation.max).toBe(100);
      expect(recommendation.idle).toBe(20);
    });

    it('should use custom configuration', () => {
      const recommendation = PoolSizeCalculator.calculate('custom', undefined, {
        minConnections: 5,
        maxConnections: 50,
      });

      expect(recommendation.min).toBe(5);
      expect(recommendation.max).toBe(50);
      expect(recommendation.idle).toBe(13); // (5+50)/4 = 13.75 -> 13
    });

    it('should fall back to defaults', () => {
      const recommendation = PoolSizeCalculator.calculate('unknown-service');

      expect(recommendation.min).toBe(2);
      expect(recommendation.max).toBe(10);
      expect(recommendation.idle).toBe(3);
    });

    it('should calculate optimal pool size', () => {
      const size = PoolSizeCalculator.getOptimalPoolSize(
        100, // 100 requests per second
        50, // 50ms average query time
        100 // 100ms target latency
      );

      expect(size).toBeGreaterThan(0);
      expect(size).toBe(50); // max(100*0.05*1.2, 100*50/100, 2) = max(6, 50, 2) = 50
    });
  });
});
