import { describe, it, expect, beforeEach, vi } from 'vitest';
import pino from 'pino';
import { GridMaker, type AdaptiveGridConfig } from '../src/gridMaker';
import type { Order, Fill, OrderBook } from '../../domain/src/types';
import type { OrderRouter } from '../../execution/src/orderRouter';
import type { HedgeEngine } from '../../hedge/src/hedgeEngine';
import type { ShadowBook } from '../../utils/src/shadowBook';
import pino from 'pino';

describe('GridMaker', () => {
  let gridMaker: GridMaker;
  let mockRouter: OrderRouter;
  let mockHedgeEngine: HedgeEngine;
  let mockShadowBook: ShadowBook;

  const testConfig = {
    symbol: 'BTC',
    gridStepBps: 100,  // 1%
    gridRangeBps: 400,  // 4%
    baseClipUsd: 500,
    maxLayers: 4,
    hedgeThresholdBase: 0.3,
    accountId: 'maker'
  };

  const mockMidPrice = 50000;

  beforeEach(() => {
    // Mock OrderRouter
    mockRouter = {
      sendLimitChild: vi.fn(async (order: Order) => {
        return `order-${order.clientId}`;
      })
    } as any;

    // Mock HedgeEngine
    mockHedgeEngine = {
      maybeHedge: vi.fn(async () => ({ hedged: 0, clientId: undefined, orderId: undefined }))
    } as any;

    // Mock ShadowBook
    mockShadowBook = {
      mid: vi.fn(() => mockMidPrice),
      snapshot: vi.fn(() => ({
        bids: [{ px: 49900, sz: 1 }],
        asks: [{ px: 50100, sz: 1 }],
        ts: Date.now()
      } as OrderBook))
    } as any;

    const mockLogger = pino({ level: 'silent' });

    gridMaker = new GridMaker(
      testConfig,
      mockRouter,
      mockHedgeEngine,
      mockShadowBook,
      mockLogger,
      undefined,
      async () => {},
      () => {}
    );
  });

  describe('initialize', () => {
    it('should initialize grid orders correctly', async () => {
      await gridMaker.initialize();

      // 应该创建 4 层买单 + 4 层卖单 = 8 个订单
      expect(mockRouter.sendLimitChild).toHaveBeenCalledTimes(8);

      const status = gridMaker.getStatus();
      expect(status.isInitialized).toBe(true);
      expect(status.totalGrids).toBe(8);
      expect(status.activeOrders).toBe(8);
    });

    it('should place buy orders below mid price', async () => {
      await gridMaker.initialize();

      const calls = (mockRouter.sendLimitChild as any).mock.calls;
      const buyOrders = calls.filter((call: any) => call[0].side === 'buy');

      // 所有买单价格应该低于 mid
      buyOrders.forEach((call: any) => {
        const order = call[0] as Order;
        expect(order.px).toBeLessThan(mockMidPrice);
        expect(order.postOnly).toBe(true);
        expect(order.tif).toBe('GTC');
      });
    });

    it('should place sell orders above mid price', async () => {
      await gridMaker.initialize();

      const calls = (mockRouter.sendLimitChild as any).mock.calls;
      const sellOrders = calls.filter((call: any) => call[0].side === 'sell');

      // 所有卖单价格应该高于 mid
      sellOrders.forEach((call: any) => {
        const order = call[0] as Order;
        expect(order.px).toBeGreaterThan(mockMidPrice);
        expect(order.postOnly).toBe(true);
      });
    });

    it('should throw error if no mid price available', async () => {
      (mockShadowBook.mid as any).mockReturnValue(undefined);

      await expect(gridMaker.initialize()).rejects.toThrow(
        'Cannot initialize grid: no mid price available'
      );
    });
  });

  describe('onFill', () => {
    beforeEach(async () => {
      await gridMaker.initialize();
      vi.clearAllMocks();
    });

    it('should place opposite order when buy order filled', async () => {
      const buyFill: Fill = {
        orderId: 'order-grid-BTC--1-' + Date.now(),
        tradeId: 'trade-1',
        symbol: 'BTC',
        side: 'buy',
        px: 49500,  // 买单在 -1% 成交
        sz: 0.01,
        fee: 0.00001,
        liquidity: 'maker',
        ts: Date.now()
      };

      await gridMaker.onFill(buyFill);

      // 应该挂一个卖单在更高价
      expect(mockRouter.sendLimitChild).toHaveBeenCalledTimes(1);

      const call = (mockRouter.sendLimitChild as any).mock.calls[0];
      const oppositeOrder = call[0] as Order;

      expect(oppositeOrder.side).toBe('sell');
      expect(oppositeOrder.px).toBeGreaterThan(buyFill.px);
      // 应该在 buyFill.px * (1 + 1%) 附近
      expect(oppositeOrder.px).toBeCloseTo(buyFill.px * 1.01, 0);
      expect(oppositeOrder.accountId).toBe('maker');
    });

    it('should place opposite order when sell order filled', async () => {
      const sellFill: Fill = {
        orderId: 'order-grid-BTC-1-' + Date.now(),
        tradeId: 'trade-2',
        symbol: 'BTC',
        side: 'sell',
        px: 50500,  // 卖单在 +1% 成交
        sz: 0.01,
        fee: 0.00001,
        liquidity: 'maker',
        ts: Date.now()
      };

      await gridMaker.onFill(sellFill);

      // 应该挂一个买单在更低价
      expect(mockRouter.sendLimitChild).toHaveBeenCalledTimes(1);

      const call = (mockRouter.sendLimitChild as any).mock.calls[0];
      const oppositeOrder = call[0] as Order;

      expect(oppositeOrder.side).toBe('buy');
      expect(oppositeOrder.px).toBeLessThan(sellFill.px);
      // 应该在 sellFill.px * (1 - 1%) 附近
      expect(oppositeOrder.px).toBeCloseTo(sellFill.px * 0.99, 0);
      expect(oppositeOrder.accountId).toBe('maker');
    });

    it('should update delta correctly', async () => {
      const buyFill: Fill = {
        orderId: 'order-grid-BTC--1-' + Date.now(),
        tradeId: 'trade-1',
        symbol: 'BTC',
        side: 'buy',
        px: 49500,
        sz: 0.1,  // 买入 0.1 BTC
        fee: 0.0001,
        liquidity: 'maker',
        ts: Date.now()
      };

      await gridMaker.onFill(buyFill);

      const status = gridMaker.getStatus();
      expect(status.currentDelta).toBe(0.1);  // Delta 应该增加 0.1
    });

    it('should trigger hedge when threshold reached', async () => {
      // 连续成交 3 笔买单，累积 Delta = 0.3（达到阈值）
      (mockHedgeEngine.maybeHedge as any).mockResolvedValueOnce({
        hedged: 0.3,
        orderId: 'hedge-order-1',
        clientId: 'hedge-1'
      });

      for (let i = 0; i < 3; i++) {
        const buyFill: Fill = {
          orderId: `order-grid-BTC--${i + 1}-` + (Date.now() + i),
          tradeId: `trade-${i}`,
          symbol: 'BTC',
          side: 'buy',
          px: 49500 - i * 100,
          sz: 0.1,
          fee: 0.0001,
          liquidity: 'maker',
          ts: Date.now() + i
        };

        await gridMaker.onFill(buyFill);
      }

      const status = gridMaker.getStatus();
      expect(status.currentDelta).toBe(0.3);
      expect(status.pendingHedges).toBe(1);

      // 应该触发对冲
      expect(mockHedgeEngine.maybeHedge).toHaveBeenCalled();
      expect(mockHedgeEngine.maybeHedge).toHaveBeenCalledWith('BTC', 0.3);
    });

    it('should ignore fill from non-grid orders', async () => {
      const externalFill: Fill = {
        orderId: 'external-order-123',  // 非网格订单
        tradeId: 'trade-ext',
        symbol: 'BTC',
        side: 'buy',
        px: 50000,
        sz: 0.5,
        fee: 0.0005,
        liquidity: 'taker',
        ts: Date.now()
      };

      await gridMaker.onFill(externalFill);

      // 不应该挂对手单
      expect(mockRouter.sendLimitChild).not.toHaveBeenCalled();

      // Delta 不应该更新
      const status = gridMaker.getStatus();
      expect(status.currentDelta).toBe(0);
    });
  });

  describe('getStatus', () => {
    it('should return correct status before initialization', () => {
      const status = gridMaker.getStatus();

      expect(status.isInitialized).toBe(false);
      expect(status.totalGrids).toBe(0);
      expect(status.activeOrders).toBe(0);
      expect(status.filledGrids).toBe(0);
      expect(status.pendingHedges).toBe(0);
    });

    it('should return correct status after initialization', async () => {
      await gridMaker.initialize();

      const status = gridMaker.getStatus();

      expect(status.isInitialized).toBe(true);
      expect(status.gridCenter).toBe(mockMidPrice);
      expect(status.totalGrids).toBe(8);
      expect(status.activeOrders).toBe(8);
      expect(status.filledGrids).toBe(0);
      expect(status.pendingHedges).toBe(0);
    });

    it('should correct delta when hedge fill arrives', async () => {
      await gridMaker.initialize();
      (mockHedgeEngine.maybeHedge as any).mockResolvedValue({
        hedged: 0.3,
        orderId: 'hedge-order-1',
        clientId: 'hedge-1'
      });

      // 累积 delta 触发对冲
      for (let i = 0; i < 3; i++) {
        const fill: Fill = {
          orderId: `order-grid-BTC-${i}-` + (Date.now() + i),
          tradeId: `trade-${i}`,
          symbol: 'BTC',
          side: 'buy',
          px: 49500,
          sz: 0.1,
          fee: 0,
          liquidity: 'maker',
          ts: Date.now() + i
        };
        await gridMaker.onFill(fill);
      }

      const before = gridMaker.getStatus();
      expect(before.currentDelta).toBe(0.3);
      expect(before.pendingHedges).toBe(1);

      const hedgeFill: Fill = {
        orderId: 'hedge-order-1',
        tradeId: 'hedge-trade-1',
        symbol: 'BTC',
        side: 'sell',
        px: 49600,
        sz: 0.3,
        fee: 0,
        liquidity: 'taker',
        ts: Date.now() + 10
      };

      await gridMaker.onHedgeFill(hedgeFill);

      const after = gridMaker.getStatus();
      expect(after.currentDelta).toBeCloseTo(0.0, 5);
      expect(after.pendingHedges).toBe(0);
    });
  });

  describe('adaptive grid behaviour', () => {
    it('adjusts grid step and triggers reset when volatility jumps', async () => {
      const adaptiveConfig: AdaptiveGridConfig = {
        enabled: true,
        volatilityWindowMinutes: 1,
        minVolatilityBps: 20,
        maxVolatilityBps: 400,
        minGridStepBps: 20,
        maxGridStepBps: 200,
        recenterEnabled: true,
        recenterThresholdBps: 150,
        recenterCooldownMs: 60_000,
        minStepChangeRatio: 0.1,
        minSamples: 2
      };

      let currentMid = mockMidPrice;
      (mockShadowBook.mid as any).mockImplementation(() => currentMid);

      const mockLogger = pino({ level: 'silent' });

      const adaptiveMaker = new GridMaker(
        { ...testConfig },
        mockRouter,
        mockHedgeEngine,
        mockShadowBook,
        mockLogger,
        adaptiveConfig,
        async () => {},
        () => {}
      );

      await adaptiveMaker.initialize();
      const resetSpy = vi.spyOn(adaptiveMaker as any, 'reset').mockResolvedValue(undefined);

      await adaptiveMaker.onTick(); // baseline

      currentMid = mockMidPrice * 1.12; // ~12% move
      await adaptiveMaker.onTick();

      const status = adaptiveMaker.getStatus();
      expect(status.adaptive?.currentGridStepBps).toBeGreaterThan(testConfig.gridStepBps);
      expect(resetSpy).toHaveBeenCalled();
      resetSpy.mockRestore();
    });
  });
});
