import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import { ConfigManager } from '../src/config-manager';
import { getProviderConfig, resetConfig } from '../src/index';
import { EnvLoader } from '../src/loaders/env.loader';
import { FileLoader } from '../src/loaders/file.loader';
import { appConfigSchema } from '../src/schemas';

const TEST_DIR = join(__dirname, 'provider-tests');

describe('Provider Configuration Tests', () => {
  let originalEnv: NodeJS.ProcessEnv;

  beforeEach(() => {
    // Save original environment
    originalEnv = { ...process.env };

    // Reset config singleton
    resetConfig();

    // Clean up test directory
    if (existsSync(TEST_DIR)) {
      rmSync(TEST_DIR, { recursive: true, force: true });
    }
    mkdirSync(TEST_DIR, { recursive: true });
  });

  afterEach(() => {
    // Restore original environment
    process.env = originalEnv;

    // Clean up
    resetConfig();
    if (existsSync(TEST_DIR)) {
      rmSync(TEST_DIR, { recursive: true, force: true });
    }
  });

  test('should load WebShare provider config from environment variables', async () => {
    // Set WebShare environment variables
    process.env.WEBSHARE_API_KEY = 'test-webshare-key';
    process.env.WEBSHARE_API_URL = 'https://custom.webshare.io/api/v2/';
    process.env.WEBSHARE_ENABLED = 'true';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.webshare).toBeDefined();
    expect(config.webshare?.apiKey).toBe('test-webshare-key');
    expect(config.webshare?.apiUrl).toBe('https://custom.webshare.io/api/v2/');
    expect(config.webshare?.enabled).toBe(true);
  });

  test('should load EOD provider config from environment variables', async () => {
    // Set EOD environment variables
    process.env.EOD_API_KEY = 'test-eod-key';
    process.env.EOD_BASE_URL = 'https://custom.eod.com/api';
    process.env.EOD_TIER = 'all-in-one';
    process.env.EOD_ENABLED = 'true';
    process.env.EOD_PRIORITY = '10';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.providers?.eod).toBeDefined();
    expect(config.providers?.eod?.apiKey).toBe('test-eod-key');
    expect(config.providers?.eod?.baseUrl).toBe('https://custom.eod.com/api');
    expect(config.providers?.eod?.tier).toBe('all-in-one');
    expect(config.providers?.eod?.enabled).toBe(true);
    expect(config.providers?.eod?.priority).toBe(10);
  });

  test('should load Interactive Brokers provider config from environment variables', async () => {
    // Set IB environment variables
    process.env.IB_GATEWAY_HOST = 'ib-gateway.example.com';
    process.env.IB_GATEWAY_PORT = '7497';
    process.env.IB_CLIENT_ID = '123';
    process.env.IB_ACCOUNT = 'DU123456';
    process.env.IB_MARKET_DATA_TYPE = 'live';
    process.env.IB_ENABLED = 'true';
    process.env.IB_PRIORITY = '5';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.providers?.ib).toBeDefined();
    expect(config.providers?.ib?.gateway.host).toBe('ib-gateway.example.com');
    expect(config.providers?.ib?.gateway.port).toBe(7497);
    expect(config.providers?.ib?.gateway.clientId).toBe(123);
    expect(config.providers?.ib?.account).toBe('DU123456');
    expect(config.providers?.ib?.marketDataType).toBe('live');
    expect(config.providers?.ib?.enabled).toBe(true);
    expect(config.providers?.ib?.priority).toBe(5);
  });

  test('should load QuoteMedia provider config from environment variables', async () => {
    // Set QM environment variables
    process.env.QM_USERNAME = 'test-qm-user';
    process.env.QM_PASSWORD = 'test-qm-pass';
    process.env.QM_BASE_URL = 'https://custom.quotemedia.com/api';
    process.env.QM_WEBMASTER_ID = 'webmaster123';
    process.env.QM_ENABLED = 'true';
    process.env.QM_PRIORITY = '15';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.providers?.qm).toBeDefined();
    expect(config.providers?.qm?.username).toBe('test-qm-user');
    expect(config.providers?.qm?.password).toBe('test-qm-pass');
    expect(config.providers?.qm?.baseUrl).toBe('https://custom.quotemedia.com/api');
    expect(config.providers?.qm?.webmasterId).toBe('webmaster123');
    expect(config.providers?.qm?.enabled).toBe(true);
    expect(config.providers?.qm?.priority).toBe(15);
  });

  test('should load Yahoo Finance provider config from environment variables', async () => {
    // Set Yahoo environment variables
    process.env.YAHOO_BASE_URL = 'https://custom.yahoo.com/api';
    process.env.YAHOO_COOKIE_JAR = 'false';
    process.env.YAHOO_CRUMB = 'test-crumb';
    process.env.YAHOO_ENABLED = 'true';
    process.env.YAHOO_PRIORITY = '20';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.providers?.yahoo).toBeDefined();
    expect(config.providers?.yahoo?.baseUrl).toBe('https://custom.yahoo.com/api');
    expect(config.providers?.yahoo?.cookieJar).toBe(false);
    expect(config.providers?.yahoo?.crumb).toBe('test-crumb');
    expect(config.providers?.yahoo?.enabled).toBe(true);
    expect(config.providers?.yahoo?.priority).toBe(20);
  });

  test('should merge file config with environment variables', async () => {
    // Create a config file
    const configDir = join(TEST_DIR, 'config');
    mkdirSync(configDir, { recursive: true });

    writeFileSync(
      join(configDir, 'development.json'),
      JSON.stringify(
        {
          providers: {
            eod: {
              name: 'EOD Historical Data',
              apiKey: 'file-eod-key',
              baseUrl: 'https://file.eod.com/api',
              tier: 'free',
              enabled: false,
              priority: 1,
            },
            yahoo: {
              name: 'Yahoo Finance',
              baseUrl: 'https://file.yahoo.com',
              enabled: true,
              priority: 2,
            },
          },
        },
        null,
        2
      )
    );

    // Set environment variables that should override file config
    process.env.EOD_API_KEY = 'env-eod-key';
    process.env.EOD_ENABLED = 'true';
    process.env.EOD_PRIORITY = '10';
    process.env.YAHOO_PRIORITY = '25';

    const manager = new ConfigManager({
      loaders: [new FileLoader(configDir, 'development'), new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    // EOD config should be merged (env overrides file)
    expect(config.providers?.eod?.name).toBe('EOD Historical Data'); // From file
    expect(config.providers?.eod?.apiKey).toBe('env-eod-key'); // From env
    expect(config.providers?.eod?.baseUrl).toBe('https://file.eod.com/api'); // From file
    expect(config.providers?.eod?.enabled).toBe(true); // From env (overrides file)
    expect(config.providers?.eod?.priority).toBe(10); // From env (overrides file)

    // Yahoo config should be merged
    expect(config.providers?.yahoo?.name).toBe('Yahoo Finance'); // From file
    expect(config.providers?.yahoo?.baseUrl).toBe('https://file.yahoo.com'); // From file
    expect(config.providers?.yahoo?.priority).toBe(25); // From env (overrides file)
  });

  test('should handle invalid provider configurations', async () => {
    // Set invalid values
    process.env.EOD_TIER = 'invalid-tier'; // Should be one of ['free', 'fundamentals', 'all-in-one']
    process.env.IB_MARKET_DATA_TYPE = 'invalid-type'; // Should be one of ['live', 'delayed', 'frozen']
    process.env.IB_GATEWAY_PORT = 'not-a-number'; // Should be a number

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    // Should throw validation error
    await expect(manager.initialize(appConfigSchema)).rejects.toThrow();
  });

  test('should work with getProviderConfig helper function', async () => {
    // Set up multiple providers
    process.env.EOD_API_KEY = 'test-eod-key';
    process.env.EOD_ENABLED = 'true';
    process.env.WEBSHARE_API_KEY = 'test-webshare-key';
    process.env.WEBSHARE_ENABLED = 'true';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    await manager.initialize(appConfigSchema);

    // Test getProviderConfig helper
    const eodConfig = getProviderConfig('eod');
    expect(eodConfig).toBeDefined();
    expect((eodConfig as any).apiKey).toBe('test-eod-key');

    const webshareConfig = getProviderConfig('webshare');
    expect(webshareConfig).toBeDefined();
    expect((webshareConfig as any).apiKey).toBe('test-webshare-key');

    // Test non-existent provider
    expect(() => getProviderConfig('nonexistent')).toThrow(
      'Provider configuration not found: nonexistent'
    );
  });

  test('should handle boolean string parsing correctly', async () => {
    // Test various boolean representations
    process.env.EOD_ENABLED = 'TRUE';
    process.env.YAHOO_ENABLED = 'False';
    process.env.IB_ENABLED = '1';
    process.env.QM_ENABLED = '0';
    process.env.WEBSHARE_ENABLED = 'yes'; // Should be treated as string, not boolean

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.providers?.eod?.enabled).toBe(true);
    expect(config.providers?.yahoo?.enabled).toBe(false);
    expect(config.providers?.ib?.enabled).toBe(true); // 1 is parsed as number, not boolean
    expect(config.providers?.qm?.enabled).toBe(false); // 0 is parsed as number, not boolean
    // webshare.enabled should be the string 'yes', but schema validation might reject it
  });

  test('should handle nested configuration correctly', async () => {
    // Test nested IB gateway configuration
    process.env.IB_GATEWAY_HOST = 'gateway.ib.com';
    process.env.IB_GATEWAY_PORT = '7497';
    process.env.IB_GATEWAY_CLIENT_ID = '999';

    const manager = new ConfigManager({
      loaders: [new EnvLoader('')],
    });

    const config = await manager.initialize(appConfigSchema);

    expect(config.providers?.ib?.gateway).toBeDefined();
    expect(config.providers?.ib?.gateway.host).toBe('gateway.ib.com');
    expect(config.providers?.ib?.gateway.port).toBe(7497);
    expect(config.providers?.ib?.gateway.clientId).toBe(999);
  });

  test('should load provider configs from .env file', async () => {
    // Create .env file with provider configs
    writeFileSync(
      join(TEST_DIR, '.env'),
      `# Provider configurations
EOD_API_KEY=env-file-eod-key
EOD_ENABLED=true
WEBSHARE_API_KEY=env-file-webshare-key
IB_GATEWAY_HOST=env-file-ib-host
IB_GATEWAY_PORT=7498
YAHOO_BASE_URL=https://env-file.yahoo.com
`
    );

    const originalCwd = process.cwd();
    try {
      process.chdir(TEST_DIR);

      const manager = new ConfigManager({
        loaders: [new EnvLoader('')],
      });

      const config = await manager.initialize(appConfigSchema);

      expect(config.providers?.eod?.apiKey).toBe('env-file-eod-key');
      expect(config.providers?.eod?.enabled).toBe(true);
      expect(config.webshare?.apiKey).toBe('env-file-webshare-key');
      expect(config.providers?.ib?.gateway.host).toBe('env-file-ib-host');
      expect(config.providers?.ib?.gateway.port).toBe(7498);
      expect(config.providers?.yahoo?.baseUrl).toBe('https://env-file.yahoo.com');
    } finally {
      process.chdir(originalCwd);
    }
  });
});
