import { beforeEach, describe, expect, it } from 'bun:test';
import { z } from 'zod';
import { baseAppSchema, ConfigManager, createAppConfig } from '../src';
import { ConfigError, ConfigValidationError } from '../src/errors';

// Mock loader for testing
class MockLoader {
  constructor(
    private data: Record<string, unknown>,
    public priority: number = 0
  ) {}

  load(): Record<string, unknown> {
    return this.data;
  }
}

describe('ConfigManager', () => {
  let manager: ConfigManager<any>;

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

  it('should initialize with default loaders', () => {
    expect(manager).toBeDefined();
  });

  it('should detect environment', () => {
    const env = manager.getEnvironment();
    expect(['development', 'test', 'production']).toContain(env);
  });

  it('should throw when getting config before initialization', () => {
    expect(() => manager.get()).toThrow(ConfigError);
  });

  it('should initialize config with schema', () => {
    const schema = z.object({
      name: z.string(),
      port: z.number(),
    });

    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ name: 'test', port: 3000 })],
    });

    const config = mockManager.initialize(schema);
    expect(config).toEqual({ name: 'test', port: 3000 });
  });

  it('should merge configs from multiple loaders', () => {
    const mockManager = new ConfigManager({
      loaders: [
        new MockLoader({ name: 'test', port: 3000 }, 1),
        new MockLoader({ port: 4000, debug: true }, 2),
      ],
    });

    const config = mockManager.initialize();
    expect(config).toEqual({ name: 'test', port: 4000, debug: true, environment: 'test' });
  });

  it('should deep merge nested objects', () => {
    const mockManager = new ConfigManager({
      loaders: [
        new MockLoader({ db: { host: 'localhost', port: 5432 } }, 1),
        new MockLoader({ db: { port: 5433, user: 'admin' } }, 2),
      ],
    });

    const config = mockManager.initialize();
    expect(config).toEqual({
      db: { host: 'localhost', port: 5433, user: 'admin' },
      environment: 'test',
    });
  });

  it('should get value by path', () => {
    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ db: { host: 'localhost', port: 5432 } })],
    });

    mockManager.initialize();
    expect(mockManager.getValue('db.host')).toBe('localhost');
    expect(mockManager.getValue('db.port')).toBe(5432);
  });

  it('should throw for non-existent path', () => {
    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ db: { host: 'localhost' } })],
    });

    mockManager.initialize();
    expect(() => mockManager.getValue('db.password')).toThrow(ConfigError);
  });

  it('should check if path exists', () => {
    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ db: { host: 'localhost' } })],
    });

    mockManager.initialize();
    expect(mockManager.has('db.host')).toBe(true);
    expect(mockManager.has('db.password')).toBe(false);
  });

  it('should update config at runtime', () => {
    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ name: 'test', port: 3000 })],
    });

    mockManager.initialize();
    mockManager.set({ port: 4000 });
    expect(mockManager.get()).toEqual({ name: 'test', port: 4000, environment: 'test' });
  });

  it('should validate config update with schema', () => {
    const schema = z.object({
      name: z.string(),
      port: z.number(),
    });

    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ name: 'test', port: 3000 })],
    });

    mockManager.initialize(schema);
    expect(() => mockManager.set({ port: 'invalid' as any })).toThrow(ConfigValidationError);
  });

  it('should reset config', () => {
    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ name: 'test' })],
    });

    mockManager.initialize();
    expect(mockManager.get()).toEqual({ name: 'test', environment: 'test' });

    mockManager.reset();
    expect(() => mockManager.get()).toThrow(ConfigError);
  });

  it('should validate against schema', () => {
    const schema = z.object({
      name: z.string(),
      port: z.number(),
    });

    const mockManager = new ConfigManager({
      loaders: [new MockLoader({ name: 'test', port: 3000 })],
    });

    mockManager.initialize();
    const validated = mockManager.validate(schema);
    expect(validated).toEqual({ name: 'test', port: 3000 });
  });

  it('should add environment if not present', () => {
    const mockManager = new ConfigManager({
      environment: 'test',
      loaders: [new MockLoader({ name: 'test' })],
    });

    const config = mockManager.initialize();
    expect(config).toEqual({ name: 'test', environment: 'test' });
  });
});

describe('Config Builders', () => {
  it('should create app config with schema', () => {
    const schema = z.object({
      app: z.string(),
      version: z.number(),
    });

    const config = createAppConfig(schema, {
      loaders: [new MockLoader({ app: 'myapp', version: 1 })],
    });

    expect(config).toBeDefined();
  });

  it('should initialize app config in one step', () => {
    const schema = z.object({
      app: z.string(),
      version: z.number(),
    });

    const configManager = createAppConfig(schema, {
      loaders: [new MockLoader({ app: 'myapp', version: 1 })],
    });
    const config = configManager.initialize(schema);

    expect(config).toEqual({ app: 'myapp', version: 1 });
  });
});

describe('Environment Detection', () => {
  it('should detect environments correctly in ConfigManager', () => {
    // Test with different environments using mock configs
    const envConfigs = [{ env: 'development' }, { env: 'production' }, { env: 'test' }];

    for (const { env } of envConfigs) {
      const mockConfig = {
        name: 'test-app',
        version: '1.0.0',
        environment: env as 'development' | 'production' | 'test',
        service: {
          name: 'test',
          port: 3000,
        },
        database: {
          mongodb: {
            uri: 'mongodb://localhost',
            database: 'test-db',
          },
          postgres: {
            host: 'localhost',
            port: 5432,
            database: 'test-db',
            user: 'test-user',
            password: 'test-pass',
          },
          questdb: {
            host: 'localhost',
            httpPort: 9000,
          },
        },
        log: {
          level: 'info' as const,
          pretty: true,
        },
        queue: {
          redis: { host: 'localhost', port: 6379 },
        },
      };

      const manager = new ConfigManager({
        loaders: [new MockLoader(mockConfig)],
        environment: env as any,
      });

      manager.initialize(baseAppSchema);

      // Test the manager's environment detection
      expect(manager.getEnvironment()).toBe(env);
      expect(manager.get().environment).toBe(env);
    }
  });
});
