import { describe, expect, it } from 'bun:test';
import { z } from 'zod';
import {
  baseConfigSchema,
  baseProviderConfigSchema,
  browserConfigSchema,
  databaseConfigSchema,
  dragonflyConfigSchema,
  environmentSchema,
  eodProviderConfigSchema,
  httpConfigSchema,
  ibProviderConfigSchema,
  loggingConfigSchema,
  mongodbConfigSchema,
  postgresConfigSchema,
  providerConfigSchema,
  proxyConfigSchema,
  qmProviderConfigSchema,
  questdbConfigSchema,
  queueConfigSchema,
  serviceConfigSchema,
  webshareConfigSchema,
  webshareProviderConfigSchema,
  yahooProviderConfigSchema,
} from '../src/schemas';

describe('Config Schemas', () => {
  describe('environmentSchema', () => {
    it('should accept valid environments', () => {
      expect(environmentSchema.parse('development')).toBe('development');
      expect(environmentSchema.parse('test')).toBe('test');
      expect(environmentSchema.parse('production')).toBe('production');
    });

    it('should reject invalid environments', () => {
      expect(() => environmentSchema.parse('staging')).toThrow();
      expect(() => environmentSchema.parse('dev')).toThrow();
      expect(() => environmentSchema.parse('')).toThrow();
    });
  });

  describe('baseConfigSchema', () => {
    it('should accept minimal valid config', () => {
      const config = baseConfigSchema.parse({});
      expect(config).toEqual({
        debug: false,
      });
    });

    it('should accept full valid config', () => {
      const input = {
        environment: 'production',
        name: 'test-app',
        version: '1.0.0',
        debug: true,
      };

      const config = baseConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should apply default values', () => {
      const config = baseConfigSchema.parse({ name: 'app' });
      expect(config.debug).toBe(false);
    });

    it('should reject invalid environment in base config', () => {
      expect(() => baseConfigSchema.parse({ environment: 'invalid' })).toThrow();
    });
  });

  describe('serviceConfigSchema', () => {
    it('should require name and port', () => {
      expect(() => serviceConfigSchema.parse({})).toThrow();
      expect(() => serviceConfigSchema.parse({ name: 'test' })).toThrow();
      expect(() => serviceConfigSchema.parse({ port: 3000 })).toThrow();
    });

    it('should accept minimal valid config', () => {
      const config = serviceConfigSchema.parse({
        name: 'test-service',
        port: 3000,
      });

      expect(config).toEqual({
        name: 'test-service',
        port: 3000,
        host: '0.0.0.0',
        healthCheckPath: '/health',
        metricsPath: '/metrics',
        shutdownTimeout: 30000,
        cors: {
          enabled: true,
          origin: '*',
          credentials: true,
        },
      });
    });

    it('should accept full config', () => {
      const input = {
        name: 'test-service',
        serviceName: 'test-service',
        port: 8080,
        host: 'localhost',
        healthCheckPath: '/api/health',
        metricsPath: '/api/metrics',
        shutdownTimeout: 60000,
        cors: {
          enabled: false,
          origin: ['http://localhost:3000', 'https://example.com'],
          credentials: false,
        },
      };

      const config = serviceConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should validate port range', () => {
      expect(() => serviceConfigSchema.parse({ name: 'test', port: 0 })).toThrow();
      expect(() => serviceConfigSchema.parse({ name: 'test', port: 65536 })).toThrow();
      expect(() => serviceConfigSchema.parse({ name: 'test', port: -1 })).toThrow();

      // Valid ports
      expect(serviceConfigSchema.parse({ name: 'test', port: 1 }).port).toBe(1);
      expect(serviceConfigSchema.parse({ name: 'test', port: 65535 }).port).toBe(65535);
    });

    it('should handle CORS origin as string or array', () => {
      const stringOrigin = serviceConfigSchema.parse({
        name: 'test',
        port: 3000,
        cors: { origin: 'http://localhost:3000' },
      });
      expect(stringOrigin.cors.origin).toBe('http://localhost:3000');

      const arrayOrigin = serviceConfigSchema.parse({
        name: 'test',
        port: 3000,
        cors: { origin: ['http://localhost:3000', 'https://example.com'] },
      });
      expect(arrayOrigin.cors.origin).toEqual(['http://localhost:3000', 'https://example.com']);
    });
  });

  describe('loggingConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = loggingConfigSchema.parse({});
      expect(config).toEqual({
        level: 'info',
        format: 'json',
        hideObject: false,
      });
    });

    it('should accept all log levels', () => {
      const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
      for (const level of levels) {
        const config = loggingConfigSchema.parse({ level });
        expect(config.level).toBe(level);
      }
    });

    it('should reject invalid log levels', () => {
      expect(() => loggingConfigSchema.parse({ level: 'verbose' })).toThrow();
      expect(() => loggingConfigSchema.parse({ level: 'warning' })).toThrow();
    });

    it('should accept loki configuration', () => {
      const config = loggingConfigSchema.parse({
        loki: {
          enabled: true,
          host: 'loki.example.com',
          port: 3100,
          labels: { app: 'test', env: 'prod' },
        },
      });

      expect(config.loki).toEqual({
        enabled: true,
        host: 'loki.example.com',
        port: 3100,
        labels: { app: 'test', env: 'prod' },
      });
    });

    it('should apply loki defaults', () => {
      const config = loggingConfigSchema.parse({
        loki: { enabled: true },
      });

      expect(config.loki).toEqual({
        enabled: true,
        host: 'localhost',
        port: 3100,
        labels: {},
      });
    });
  });

  describe('queueConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = queueConfigSchema.parse({
        redis: {}, // redis is required, but its properties have defaults
      });
      expect(config).toEqual({
        enabled: true,
        redis: {
          host: 'localhost',
          port: 6379,
          db: 1,
        },
        workers: 1,
        concurrency: 1,
        enableScheduledJobs: true,
        defaultJobOptions: {
          attempts: 3,
          backoff: {
            type: 'exponential',
            delay: 1000,
          },
          removeOnComplete: 100,
          removeOnFail: 100,
        },
      });
    });

    it('should accept full config', () => {
      const input = {
        enabled: false,
        redis: {
          host: 'redis.example.com',
          port: 6380,
          password: 'secret',
          db: 2,
        },
        workers: 4,
        concurrency: 10,
        enableScheduledJobs: false,
        defaultJobOptions: {
          attempts: 5,
          backoff: {
            type: 'fixed' as const,
            delay: 2000,
          },
          removeOnComplete: 50,
          removeOnFail: 200,
          timeout: 60000,
        },
      };

      const config = queueConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should validate backoff type', () => {
      const exponential = queueConfigSchema.parse({
        redis: {},
        defaultJobOptions: { backoff: { type: 'exponential' } },
      });
      expect(exponential.defaultJobOptions.backoff.type).toBe('exponential');

      const fixed = queueConfigSchema.parse({
        redis: {},
        defaultJobOptions: { backoff: { type: 'fixed' } },
      });
      expect(fixed.defaultJobOptions.backoff.type).toBe('fixed');

      expect(() =>
        queueConfigSchema.parse({
          redis: {},
          defaultJobOptions: { backoff: { type: 'linear' } },
        })
      ).toThrow();
    });
  });

  describe('httpConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = httpConfigSchema.parse({});
      expect(config).toEqual({
        timeout: 30000,
        retries: 3,
        retryDelay: 1000,
      });
    });

    it('should accept full config', () => {
      const input = {
        timeout: 60000,
        retries: 5,
        retryDelay: 2000,
        userAgent: 'MyApp/1.0',
        proxy: {
          enabled: true,
          url: 'http://proxy.example.com:8080',
          auth: {
            username: 'user',
            password: 'pass',
          },
        },
      };

      const config = httpConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should validate proxy URL', () => {
      expect(() =>
        httpConfigSchema.parse({
          proxy: { url: 'not-a-url' },
        })
      ).toThrow();

      const validProxy = httpConfigSchema.parse({
        proxy: { url: 'http://proxy.example.com' },
      });
      expect(validProxy.proxy?.url).toBe('http://proxy.example.com');
    });
  });

  describe('webshareConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = webshareConfigSchema.parse({});
      expect(config).toEqual({
        apiUrl: 'https://proxy.webshare.io/api/v2/',
        enabled: true,
      });
    });

    it('should accept full config', () => {
      const input = {
        apiKey: 'test-api-key',
        apiUrl: 'https://custom.webshare.io/api/v3/',
        enabled: false,
      };

      const config = webshareConfigSchema.parse(input);
      expect(config).toEqual(input);
    });
  });

  describe('browserConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = browserConfigSchema.parse({});
      expect(config).toEqual({
        headless: true,
        timeout: 30000,
      });
    });

    it('should accept custom values', () => {
      const config = browserConfigSchema.parse({
        headless: false,
        timeout: 60000,
      });
      expect(config).toEqual({
        headless: false,
        timeout: 60000,
      });
    });
  });

  describe('proxyConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = proxyConfigSchema.parse({});
      expect(config).toEqual({
        enabled: false,
        cachePrefix: 'proxy:',
        ttl: 3600,
      });
    });

    it('should accept full config', () => {
      const input = {
        enabled: true,
        cachePrefix: 'custom:proxy:',
        ttl: 7200,
        webshare: {
          apiKey: 'test-key',
          apiUrl: 'https://api.webshare.io/v2/',
        },
      };

      const config = proxyConfigSchema.parse(input);
      expect(config).toEqual(input);
    });
  });

  describe('Schema Composition', () => {
    it('should be able to compose schemas', () => {
      const appConfigSchema = z.object({
        base: baseConfigSchema,
        service: serviceConfigSchema,
        logging: loggingConfigSchema,
      });

      const config = appConfigSchema.parse({
        base: {
          name: 'test-app',
          version: '1.0.0',
        },
        service: {
          name: 'test-service',
          port: 3000,
        },
        logging: {
          level: 'debug',
        },
      });

      expect(config.base.debug).toBe(false);
      expect(config.service.host).toBe('0.0.0.0');
      expect(config.logging.format).toBe('json');
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty strings appropriately', () => {
      // Empty strings are allowed by z.string() unless .min(1) is specified
      const serviceConfig = serviceConfigSchema.parse({ name: '', port: 3000 });
      expect(serviceConfig.name).toBe('');

      const baseConfig = baseConfigSchema.parse({ name: '' });
      expect(baseConfig.name).toBe('');
    });

    it('should handle null values', () => {
      expect(() => serviceConfigSchema.parse({ name: null, port: 3000 })).toThrow();
      expect(() => queueConfigSchema.parse({ redis: {}, workers: null })).toThrow();
    });

    it('should handle undefined values for optional fields', () => {
      const config = serviceConfigSchema.parse({
        name: 'test',
        port: 3000,
        serviceName: undefined,
      });
      expect(config.serviceName).toBeUndefined();
    });

    it('should handle numeric strings for number fields', () => {
      expect(() => serviceConfigSchema.parse({ name: 'test', port: '3000' })).toThrow();
      expect(() => queueConfigSchema.parse({ redis: {}, workers: '4' })).toThrow();
    });

    it('should strip unknown properties', () => {
      const config = baseConfigSchema.parse({
        name: 'test',
        unknownProp: 'should be removed',
      });
      expect('unknownProp' in config).toBe(false);
    });
  });

  describe('postgresConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = postgresConfigSchema.parse({
        database: 'testdb',
        user: 'testuser',
        password: 'testpass',
      });
      expect(config).toEqual({
        enabled: true,
        host: 'localhost',
        port: 5432,
        database: 'testdb',
        user: 'testuser',
        password: 'testpass',
        ssl: false,
        poolSize: 10,
        connectionTimeout: 30000,
        idleTimeout: 10000,
      });
    });

    it('should accept full config', () => {
      const input = {
        enabled: false,
        host: 'db.example.com',
        port: 5433,
        database: 'proddb',
        user: 'admin',
        password: 'secret',
        ssl: true,
        poolSize: 20,
        connectionTimeout: 60000,
        idleTimeout: 30000,
      };
      const config = postgresConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should validate poolSize range', () => {
      expect(() =>
        postgresConfigSchema.parse({
          database: 'testdb',
          user: 'testuser',
          password: 'testpass',
          poolSize: 0,
        })
      ).toThrow();

      expect(() =>
        postgresConfigSchema.parse({
          database: 'testdb',
          user: 'testuser',
          password: 'testpass',
          poolSize: 101,
        })
      ).toThrow();
    });
  });

  describe('questdbConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = questdbConfigSchema.parse({});
      expect(config).toEqual({
        enabled: true,
        host: 'localhost',
        ilpPort: 9009,
        httpPort: 9000,
        pgPort: 8812,
        database: 'questdb',
        bufferSize: 65536,
        flushInterval: 1000,
      });
    });

    it('should accept full config', () => {
      const input = {
        enabled: false,
        host: 'questdb.example.com',
        ilpPort: 9010,
        httpPort: 9001,
        pgPort: 8813,
        database: 'metrics',
        user: 'admin',
        password: 'secret',
        bufferSize: 131072,
        flushInterval: 2000,
      };
      const config = questdbConfigSchema.parse(input);
      expect(config).toEqual(input);
    });
  });

  describe('mongodbConfigSchema', () => {
    it('should accept minimal config', () => {
      const config = mongodbConfigSchema.parse({
        uri: 'mongodb://localhost:27017',
        database: 'testdb',
      });
      expect(config).toEqual({
        enabled: true,
        uri: 'mongodb://localhost:27017',
        database: 'testdb',
        poolSize: 10,
      });
    });

    it('should accept full config', () => {
      const input = {
        enabled: false,
        uri: 'mongodb://user:pass@cluster.mongodb.net',
        database: 'proddb',
        poolSize: 50,
        host: 'cluster.mongodb.net',
        port: 27017,
        user: 'admin',
        password: 'secret',
        authSource: 'admin',
        replicaSet: 'rs0',
      };
      const config = mongodbConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should validate URI format', () => {
      expect(() =>
        mongodbConfigSchema.parse({
          uri: 'invalid-uri',
          database: 'testdb',
        })
      ).toThrow();
    });

    it('should validate poolSize range', () => {
      expect(() =>
        mongodbConfigSchema.parse({
          uri: 'mongodb://localhost',
          database: 'testdb',
          poolSize: 0,
        })
      ).toThrow();

      expect(() =>
        mongodbConfigSchema.parse({
          uri: 'mongodb://localhost',
          database: 'testdb',
          poolSize: 101,
        })
      ).toThrow();
    });
  });

  describe('dragonflyConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = dragonflyConfigSchema.parse({});
      expect(config).toEqual({
        enabled: true,
        host: 'localhost',
        port: 6379,
        db: 0,
        maxRetries: 3,
        retryDelay: 100,
      });
    });

    it('should accept full config', () => {
      const input = {
        enabled: false,
        host: 'cache.example.com',
        port: 6380,
        password: 'secret',
        db: 5,
        keyPrefix: 'app:',
        ttl: 3600,
        maxRetries: 5,
        retryDelay: 200,
      };
      const config = dragonflyConfigSchema.parse(input);
      expect(config).toEqual(input);
    });

    it('should validate db range', () => {
      expect(() => dragonflyConfigSchema.parse({ db: -1 })).toThrow();
      expect(() => dragonflyConfigSchema.parse({ db: 16 })).toThrow();
    });
  });

  describe('databaseConfigSchema', () => {
    it('should accept complete database configuration', () => {
      const config = databaseConfigSchema.parse({
        postgres: {
          database: 'testdb',
          user: 'testuser',
          password: 'testpass',
        },
        questdb: {},
        mongodb: {
          uri: 'mongodb://localhost',
          database: 'testdb',
        },
        dragonfly: {},
      });

      expect(config.postgres.host).toBe('localhost');
      expect(config.questdb.enabled).toBe(true);
      expect(config.mongodb.poolSize).toBe(10);
      expect(config.dragonfly.port).toBe(6379);
    });
  });

  describe('baseProviderConfigSchema', () => {
    it('should accept minimal config with defaults', () => {
      const config = baseProviderConfigSchema.parse({
        name: 'test-provider',
      });
      expect(config).toEqual({
        name: 'test-provider',
        enabled: true,
        priority: 0,
        timeout: 30000,
        retries: 3,
      });
    });

    it('should accept full config', () => {
      const input = {
        name: 'test-provider',
        enabled: false,
        priority: 10,
        rateLimit: {
          maxRequests: 50,
          windowMs: 30000,
        },
        timeout: 60000,
        retries: 5,
      };
      const config = baseProviderConfigSchema.parse(input);
      expect(config).toEqual(input);
    });
  });

  describe('eodProviderConfigSchema', () => {
    it('should accept minimal config', () => {
      const config = eodProviderConfigSchema.parse({
        name: 'eod',
        apiKey: 'test-key',
      });
      expect(config).toEqual({
        name: 'eod',
        apiKey: 'test-key',
        enabled: true,
        priority: 0,
        timeout: 30000,
        retries: 3,
        baseUrl: 'https://eodhistoricaldata.com/api',
        tier: 'free',
      });
    });

    it('should validate tier values', () => {
      expect(() =>
        eodProviderConfigSchema.parse({
          name: 'eod',
          apiKey: 'test-key',
          tier: 'premium',
        })
      ).toThrow();

      const validTiers = ['free', 'fundamentals', 'all-in-one'];
      for (const tier of validTiers) {
        const config = eodProviderConfigSchema.parse({
          name: 'eod',
          apiKey: 'test-key',
          tier,
        });
        expect(config.tier).toBe(tier);
      }
    });
  });

  describe('ibProviderConfigSchema', () => {
    it('should accept minimal config', () => {
      const config = ibProviderConfigSchema.parse({
        name: 'ib',
      });
      expect(config).toEqual({
        name: 'ib',
        enabled: true,
        priority: 0,
        timeout: 30000,
        retries: 3,
        gateway: {
          host: 'localhost',
          port: 5000,
          clientId: 1,
        },
        marketDataType: 'delayed',
      });
    });

    it('should accept full config', () => {
      const input = {
        name: 'ib',
        enabled: false,
        priority: 5,
        gateway: {
          host: 'gateway.example.com',
          port: 7497,
          clientId: 99,
        },
        account: 'DU123456',
        marketDataType: 'live' as const,
      };
      const config = ibProviderConfigSchema.parse(input);
      expect(config).toEqual(expect.objectContaining(input));
    });

    it('should validate marketDataType', () => {
      expect(() =>
        ibProviderConfigSchema.parse({
          name: 'ib',
          marketDataType: 'realtime',
        })
      ).toThrow();

      const validTypes = ['live', 'delayed', 'frozen'];
      for (const type of validTypes) {
        const config = ibProviderConfigSchema.parse({
          name: 'ib',
          marketDataType: type,
        });
        expect(config.marketDataType).toBe(type);
      }
    });
  });

  describe('qmProviderConfigSchema', () => {
    it('should require all credentials', () => {
      expect(() =>
        qmProviderConfigSchema.parse({
          name: 'qm',
        })
      ).toThrow();

      const config = qmProviderConfigSchema.parse({
        name: 'qm',
        username: 'testuser',
        password: 'testpass',
        webmasterId: '12345',
      });
      expect(config.baseUrl).toBe('https://app.quotemedia.com/quotetools');
    });
  });

  describe('yahooProviderConfigSchema', () => {
    it('should accept minimal config', () => {
      const config = yahooProviderConfigSchema.parse({
        name: 'yahoo',
      });
      expect(config).toEqual({
        name: 'yahoo',
        enabled: true,
        priority: 0,
        timeout: 30000,
        retries: 3,
        baseUrl: 'https://query1.finance.yahoo.com',
        cookieJar: true,
      });
    });

    it('should accept crumb parameter', () => {
      const config = yahooProviderConfigSchema.parse({
        name: 'yahoo',
        crumb: 'abc123xyz',
      });
      expect(config.crumb).toBe('abc123xyz');
    });
  });

  describe('webshareProviderConfigSchema', () => {
    it('should not require name like other providers', () => {
      const config = webshareProviderConfigSchema.parse({});
      expect(config).toEqual({
        apiUrl: 'https://proxy.webshare.io/api/v2/',
        enabled: true,
      });
    });

    it('should accept apiKey', () => {
      const config = webshareProviderConfigSchema.parse({
        apiKey: 'test-key',
        enabled: false,
      });
      expect(config.apiKey).toBe('test-key');
      expect(config.enabled).toBe(false);
    });
  });

  describe('providerConfigSchema', () => {
    it('should accept empty config', () => {
      const config = providerConfigSchema.parse({});
      expect(config).toEqual({});
    });

    it('should accept partial provider config', () => {
      const config = providerConfigSchema.parse({
        eod: {
          name: 'eod',
          apiKey: 'test-key',
        },
        yahoo: {
          name: 'yahoo',
        },
      });
      expect(config.eod?.apiKey).toBe('test-key');
      expect(config.yahoo?.baseUrl).toBe('https://query1.finance.yahoo.com');
      expect(config.ib).toBeUndefined();
    });

    it('should accept full provider config', () => {
      const config = providerConfigSchema.parse({
        eod: {
          name: 'eod',
          apiKey: 'eod-key',
          tier: 'all-in-one',
        },
        ib: {
          name: 'ib',
          gateway: {
            host: 'gateway.ib.com',
            port: 7497,
            clientId: 2,
          },
        },
        qm: {
          name: 'qm',
          username: 'user',
          password: 'pass',
          webmasterId: '123',
        },
        yahoo: {
          name: 'yahoo',
          crumb: 'xyz',
        },
        webshare: {
          apiKey: 'ws-key',
        },
      });

      expect(config.eod?.tier).toBe('all-in-one');
      expect(config.ib?.gateway.port).toBe(7497);
      expect(config.qm?.username).toBe('user');
      expect(config.yahoo?.crumb).toBe('xyz');
      expect(config.webshare?.apiKey).toBe('ws-key');
    });
  });
});
