import { mkdirSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import {
  getConfig,
  getConfigManager,
  getDatabaseConfig,
  getLoggingConfig,
  getProviderConfig,
  getServiceConfig,
  initializeConfig,
  isDevelopment,
  isProduction,
  isTest,
  resetConfig,
} from '../src';

describe('Config Module', () => {
  const testConfigDir = join(process.cwd(), 'test-config-module');
  const originalEnv = { ...process.env };

  beforeEach(() => {
    resetConfig();
    mkdirSync(testConfigDir, { recursive: true });

    // Create test configuration files
    const config = {
      name: 'test-app',
      version: '1.0.0',
      service: {
        name: 'test-service',
        port: 3000,
      },
      database: {
        postgres: {
          host: 'localhost',
          port: 5432,
          database: 'testdb',
          user: 'testuser',
          password: 'testpass',
        },
        questdb: {
          host: 'localhost',
          httpPort: 9000,
          pgPort: 8812,
        },
        mongodb: {
          host: 'localhost',
          port: 27017,
          database: 'testdb',
        },
        dragonfly: {
          host: 'localhost',
          port: 6379,
        },
      },
      logging: {
        level: 'info',
        format: 'json',
      },
      providers: {
        yahoo: {
          enabled: true,
          rateLimit: 5,
        },
        qm: {
          enabled: false,
          apiKey: 'test-key',
        },
      },
      environment: 'test',
    };

    writeFileSync(join(testConfigDir, 'default.json'), JSON.stringify(config, null, 2));
  });

  afterEach(() => {
    resetConfig();
    rmSync(testConfigDir, { recursive: true, force: true });
    process.env = { ...originalEnv };
  });

  test('should initialize configuration', async () => {
    const config = await initializeConfig(testConfigDir);

    expect(config.app.name).toBe('test-app');
    expect(config.service.port).toBe(3000);
    expect(config.environment).toBe('test');
  });

  test('should get configuration after initialization', async () => {
    await initializeConfig(testConfigDir);
    const config = getConfig();

    expect(config.app.name).toBe('test-app');
    expect(config.database.postgres.host).toBe('localhost');
  });

  test('should throw if getting config before initialization', () => {
    expect(() => getConfig()).toThrow('Configuration not initialized');
  });

  test('should get config manager instance', async () => {
    await initializeConfig(testConfigDir);
    const manager = getConfigManager();

    expect(manager).toBeDefined();
    expect(manager.get().app.name).toBe('test-app');
  });

  test('should get database configuration', async () => {
    await initializeConfig(testConfigDir);
    const dbConfig = getDatabaseConfig();

    expect(dbConfig.postgres.host).toBe('localhost');
    expect(dbConfig.questdb.httpPort).toBe(9000);
    expect(dbConfig.mongodb.database).toBe('testdb');
  });

  test('should get service configuration', async () => {
    await initializeConfig(testConfigDir);
    const serviceConfig = getServiceConfig();

    expect(serviceConfig.name).toBe('test-service');
    expect(serviceConfig.port).toBe(3000);
  });

  test('should get logging configuration', async () => {
    await initializeConfig(testConfigDir);
    const loggingConfig = getLoggingConfig();

    expect(loggingConfig.level).toBe('info');
    expect(loggingConfig.format).toBe('json');
  });

  test('should get provider configuration', async () => {
    await initializeConfig(testConfigDir);

    const yahooConfig = getProviderConfig('yahoo');
    expect(yahooConfig.enabled).toBe(true);
    expect(yahooConfig.rateLimit).toBe(5);

    const qmConfig = getProviderConfig('quoteMedia');
    expect(qmConfig.enabled).toBe(false);
    expect(qmConfig.apiKey).toBe('test-key');
  });

  test('should throw for non-existent provider', async () => {
    await initializeConfig(testConfigDir);

    expect(() => getProviderConfig('nonexistent')).toThrow(
      'Provider configuration not found: nonexistent'
    );
  });

  test('should check environment correctly', async () => {
    await initializeConfig(testConfigDir);

    expect(isTest()).toBe(true);
    expect(isDevelopment()).toBe(false);
    expect(isProduction()).toBe(false);
  });

  test('should handle environment overrides', async () => {
    process.env.NODE_ENV = 'production';
    process.env.STOCKBOT_APP__NAME = 'env-override-app';
    process.env.STOCKBOT_DATABASE__POSTGRES__HOST = 'prod-db';

    const prodConfig = {
      database: {
        postgres: {
          host: 'prod-host',
          port: 5432,
        },
      },
    };

    writeFileSync(join(testConfigDir, 'production.json'), JSON.stringify(prodConfig, null, 2));

    const config = await initializeConfig(testConfigDir);

    expect(config.environment).toBe('production');
    expect(config.app.name).toBe('env-override-app');
    expect(config.database.postgres.host).toBe('prod-db');
    expect(isProduction()).toBe(true);
  });

  test('should reset configuration', async () => {
    await initializeConfig(testConfigDir);
    expect(() => getConfig()).not.toThrow();

    resetConfig();
    expect(() => getConfig()).toThrow('Configuration not initialized');
  });

  test('should maintain singleton instance', async () => {
    const config1 = await initializeConfig(testConfigDir);
    const config2 = await initializeConfig(testConfigDir);

    expect(config1).toBe(config2);
  });
});
