const { determineSharpIndicator } = require('../../src/services/lineMovementService');

// Mock Redis and Supabase for the service-level tests
const mockRedis = { get: jest.fn(), set: jest.fn() };
jest.mock('../../src/utils/redis', () => ({ getRedisClient: () => mockRedis }));

const mockSupabaseFrom = jest.fn();
jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({ from: mockSupabaseFrom }),
}));

const { processNewOdds, detectMovements, captureBaseline } = require('../../src/services/lineMovementService');

function makeProp(overrides = {}) {
  return {
    player: 'Nikola Jokic',
    stat_type: 'points',
    book: 'draftkings',
    line: 26.5,
    over_odds: -110,
    under_odds: -110,
    ...overrides,
  };
}

beforeEach(() => {
  jest.clearAllMocks();
  mockRedis.get.mockResolvedValue(null);
  mockRedis.set.mockResolvedValue('OK');
});

describe('lineMovementService', () => {
  describe('processNewOdds', () => {
    test('first fetch of the day captures baseline, returns no movements', async () => {
      mockRedis.get.mockResolvedValue(null); // no baseline flag
      mockSupabaseFrom.mockReturnValue({
        upsert: () => Promise.resolve({ data: [], error: null }),
        delete: () => ({ lt: () => Promise.resolve({ error: null }) }),
      });

      const result = await processNewOdds('nba', [makeProp()]);
      expect(result.isBaselineCapture).toBe(true);
      expect(result.movements).toEqual([]);
    });

    test('subsequent fetch detects movement >= 0.5', async () => {
      mockRedis.get.mockResolvedValue('1'); // baseline exists

      const baselines = [
        { player: 'Nikola Jokic', stat_type: 'points', book: 'draftkings', baseline_line: 26.5 },
      ];
      mockSupabaseFrom.mockImplementation((table) => {
        if (table === 'line_baselines') {
          return { select: () => ({ eq: () => Promise.resolve({ data: baselines }) }) };
        }
        if (table === 'line_movements') {
          return { insert: () => Promise.resolve({ error: null }) };
        }
        return {};
      });

      const result = await processNewOdds('nba', [makeProp({ line: 27.5 })]);
      expect(result.isBaselineCapture).toBe(false);
      expect(result.movements).toHaveLength(1);
      expect(result.movements[0].movement).toBe(1.0);
      expect(result.movements[0].direction).toBe('up');
    });

    test('movement < 0.5 is not flagged', async () => {
      mockRedis.get.mockResolvedValue('1');

      const baselines = [
        { player: 'Nikola Jokic', stat_type: 'points', book: 'draftkings', baseline_line: 26.5 },
      ];
      mockSupabaseFrom.mockImplementation((table) => {
        if (table === 'line_baselines') {
          return { select: () => ({ eq: () => Promise.resolve({ data: baselines }) }) };
        }
        if (table === 'line_movements') {
          return { insert: () => Promise.resolve({ error: null }) };
        }
        return {};
      });

      const result = await processNewOdds('nba', [makeProp({ line: 26.8 })]);
      expect(result.movements).toHaveLength(0);
    });

    test('direction correctly identified (down)', async () => {
      mockRedis.get.mockResolvedValue('1');

      const baselines = [
        { player: 'Nikola Jokic', stat_type: 'points', book: 'draftkings', baseline_line: 27.0 },
      ];
      mockSupabaseFrom.mockImplementation((table) => {
        if (table === 'line_baselines') {
          return { select: () => ({ eq: () => Promise.resolve({ data: baselines }) }) };
        }
        if (table === 'line_movements') {
          return { insert: () => Promise.resolve({ error: null }) };
        }
        return {};
      });

      const result = await processNewOdds('nba', [makeProp({ line: 26.0 })]);
      expect(result.movements[0].direction).toBe('down');
    });
  });

  describe('determineSharpIndicator', () => {
    test('line up + over expensive = sharp_action', () => {
      expect(determineSharpIndicator(26.5, 27.0, -120, -100, 'up')).toBe('sharp_action');
    });

    test('line up + under expensive = public_action', () => {
      expect(determineSharpIndicator(26.5, 27.0, -100, -120, 'up')).toBe('public_action');
    });

    test('line down + under expensive = sharp_action', () => {
      expect(determineSharpIndicator(27.0, 26.5, -100, -120, 'down')).toBe('sharp_action');
    });

    test('no odds = unknown', () => {
      expect(determineSharpIndicator(26.5, 27.0, null, null, 'up')).toBe('unknown');
    });
  });
});
