import { describe, expect, it } from 'bun:test';
import {
  calculateLogReturns,
  calculateReturns,
  calculateSMA,
  calculateTrueRange,
  calculateTypicalPrice,
  calculateVWAP,
  convertTimestamps,
  // Common utilities
  createProxyUrl,
  // Date utilities
  dateUtils,
  // Generic functions
  extractCloses,
  extractOHLC,
  extractVolumes,
  filterBySymbol,
  filterByTimeRange,
  groupBySymbol,
  sleep,
} from '../src/index';

describe('Utility Functions', () => {
  describe('common utilities', () => {
    it('should create proxy URL with auth', () => {
      const proxy = {
        protocol: 'http',
        host: '192.168.1.1',
        port: 8080,
        username: 'user',
        password: 'pass',
      };

      const url = createProxyUrl(proxy);
      expect(url).toBe('http://user:pass@192.168.1.1:8080');
    });

    it('should create proxy URL without auth', () => {
      const proxy = {
        protocol: 'socks5',
        host: '192.168.1.1',
        port: 1080,
      };

      const url = createProxyUrl(proxy);
      expect(url).toBe('socks5://192.168.1.1:1080');
    });

    it('should sleep for specified milliseconds', async () => {
      const start = Date.now();
      await sleep(100);
      const elapsed = Date.now() - start;

      expect(elapsed).toBeGreaterThanOrEqual(90);
      expect(elapsed).toBeLessThan(200);
    });
  });

  describe('date utilities', () => {
    it('should check if date is trading day', () => {
      const monday = new Date('2023-12-25'); // Monday
      const saturday = new Date('2023-12-23'); // Saturday
      const sunday = new Date('2023-12-24'); // Sunday

      expect(dateUtils.isTradingDay(monday)).toBe(true);
      expect(dateUtils.isTradingDay(saturday)).toBe(false);
      expect(dateUtils.isTradingDay(sunday)).toBe(false);
    });

    it('should get next trading day', () => {
      const friday = new Date('2023-12-22'); // Friday
      const nextDay = dateUtils.getNextTradingDay(friday);

      expect(nextDay.getDay()).toBe(1); // Monday
    });

    it('should get previous trading day', () => {
      const monday = new Date('2023-12-25'); // Monday
      const prevDay = dateUtils.getPreviousTradingDay(monday);

      expect(prevDay.getDay()).toBe(5); // Friday
    });

    it('should format date as YYYY-MM-DD', () => {
      const date = new Date('2023-12-25T10:30:00Z');
      const formatted = dateUtils.formatDate(date);

      expect(formatted).toBe('2023-12-25');
    });

    it('should parse date from string', () => {
      const date = dateUtils.parseDate('2023-12-25');

      expect(date.getFullYear()).toBe(2023);
      expect(date.getMonth()).toBe(11); // 0-based
      expect(date.getDate()).toBe(25);
    });
  });

  describe('generic functions', () => {
    const testData = [
      { open: 100, high: 105, low: 98, close: 103, volume: 1000 },
      { open: 103, high: 107, low: 101, close: 105, volume: 1200 },
      { open: 105, high: 108, low: 104, close: 106, volume: 1100 },
    ];

    it('should extract close prices', () => {
      const closes = extractCloses(testData);
      expect(closes).toEqual([103, 105, 106]);
    });

    it('should extract OHLC data', () => {
      const ohlc = extractOHLC(testData);

      expect(ohlc.opens).toEqual([100, 103, 105]);
      expect(ohlc.highs).toEqual([105, 107, 108]);
      expect(ohlc.lows).toEqual([98, 101, 104]);
      expect(ohlc.closes).toEqual([103, 105, 106]);
    });

    it('should extract volumes', () => {
      const volumes = extractVolumes(testData);
      expect(volumes).toEqual([1000, 1200, 1100]);
    });

    it('should calculate SMA', () => {
      const sma = calculateSMA(testData, 2);
      expect(sma).toHaveLength(2);
      expect(sma[0]).toBe(104);
      expect(sma[1]).toBe(105.5);
    });

    it('should calculate typical price', () => {
      const typical = calculateTypicalPrice(testData);

      expect(typical[0]).toBeCloseTo((105 + 98 + 103) / 3);
      expect(typical[1]).toBeCloseTo((107 + 101 + 105) / 3);
      expect(typical[2]).toBeCloseTo((108 + 104 + 106) / 3);
    });

    it('should calculate true range', () => {
      const tr = calculateTrueRange(testData);

      expect(tr).toHaveLength(3);
      expect(tr[0]).toBe(7); // 105 - 98
    });

    it('should calculate returns', () => {
      const returns = calculateReturns(testData);

      expect(returns).toHaveLength(2);
      expect(returns[0]).toBeCloseTo((105 - 103) / 103);
      expect(returns[1]).toBeCloseTo((106 - 105) / 105);
    });

    it('should calculate log returns', () => {
      const logReturns = calculateLogReturns(testData);

      expect(logReturns).toHaveLength(2);
      expect(logReturns[0]).toBeCloseTo(Math.log(105 / 103));
      expect(logReturns[1]).toBeCloseTo(Math.log(106 / 105));
    });

    it('should calculate VWAP', () => {
      const vwap = calculateVWAP(testData);

      expect(vwap).toHaveLength(3);
      expect(vwap[0]).toBeGreaterThan(0);
    });
  });

  describe('OHLCV data operations', () => {
    const ohlcvData = [
      {
        symbol: 'AAPL',
        open: 100,
        high: 105,
        low: 98,
        close: 103,
        volume: 1000,
        timestamp: 1000000,
      },
      {
        symbol: 'GOOGL',
        open: 200,
        high: 205,
        low: 198,
        close: 203,
        volume: 2000,
        timestamp: 1000000,
      },
      {
        symbol: 'AAPL',
        open: 103,
        high: 107,
        low: 101,
        close: 105,
        volume: 1200,
        timestamp: 2000000,
      },
    ];

    it('should filter by symbol', () => {
      const filtered = filterBySymbol(ohlcvData, 'AAPL');

      expect(filtered).toHaveLength(2);
      expect(filtered.every(item => item.symbol === 'AAPL')).toBe(true);
    });

    it('should filter by time range', () => {
      const filtered = filterByTimeRange(ohlcvData, 1500000, 2500000);

      expect(filtered).toHaveLength(1);
      expect(filtered[0].timestamp).toBe(2000000);
    });

    it('should group by symbol', () => {
      const grouped = groupBySymbol(ohlcvData);

      expect(grouped['AAPL']).toHaveLength(2);
      expect(grouped['GOOGL']).toHaveLength(1);
    });

    it('should convert timestamps to dates', () => {
      const converted = convertTimestamps(ohlcvData);

      expect(converted[0].date).toBeInstanceOf(Date);
      expect(converted[0].date.getTime()).toBe(1000000);
    });
  });
});
