const mockState = { grade: null, closing: null, updated: [] };

jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({
    from(table) {
      const proxy = {
        select() { return proxy; },
        eq() { return proxy; },
        gte() { return Promise.resolve({ data: mockState.summaryRows || [], error: null }); },
        not() { return proxy; },
        maybeSingle() {
          if (table === 'grade_history') return Promise.resolve({ data: mockState.grade, error: null });
          if (table === 'closing_lines') return Promise.resolve({ data: mockState.closing, error: null });
          return Promise.resolve({ data: null, error: null });
        },
        update(patch) {
          mockState.updated.push({ table, patch });
          return { eq() { return Promise.resolve({ error: null }); } };
        },
        then(resolve) { return resolve({ data: mockState.summaryRows || [], error: null }); },
      };
      return proxy;
    },
  }),
}));

const clv = require('../../src/services/intelligence/clvTracker');

beforeEach(() => {
  mockState.grade = null;
  mockState.closing = null;
  mockState.summaryRows = [];
  mockState.updated.length = 0;
});

describe('rawCLV', () => {
  test('over: closing > graded → positive CLV', () => {
    expect(clv.rawCLV('over', 25.5, 27.5)).toBe(2.0);
  });
  test('over: closing < graded → negative CLV', () => {
    expect(clv.rawCLV('over', 25.5, 24.5)).toBe(-1.0);
  });
  test('under: closing < graded → positive CLV (we were right, line moved our way)', () => {
    expect(clv.rawCLV('under', 25.5, 23.5)).toBe(2.0);
  });
  test('under: closing > graded → negative', () => {
    expect(clv.rawCLV('under', 25.5, 27.5)).toBe(-2.0);
  });
  test('invalid input → null', () => {
    expect(clv.rawCLV('over', null, 27)).toBeNull();
    expect(clv.rawCLV('over', 25, 'abc')).toBeNull();
  });
});

describe('computeCLV', () => {
  test('returns null when grade not found', async () => {
    mockState.grade = null;
    const result = await clv.computeCLV('missing');
    expect(result).toBeNull();
  });

  test('returns clv:null with reason when no closing line', async () => {
    mockState.grade = { id: 'g1', game_id: 'gm', sport: 'nba', stat_type: 'points', line: 25.5, direction: 'over', player_id: '1' };
    mockState.closing = null;
    const result = await clv.computeCLV('g1');
    expect(result.clv).toBeNull();
    expect(result.reason).toBe('no_closing_line');
  });

  test('persists CLV when both lines exist', async () => {
    mockState.grade = { id: 'g2', game_id: 'gm', sport: 'nba', stat_type: 'points', line: 25.5, direction: 'over', player_id: '1' };
    mockState.closing = { id: 'cl-1', pinnacle_line: 27.5 };
    const result = await clv.computeCLV('g2');
    expect(result.clv).toBe(2.0);
    expect(mockState.updated.some((u) => u.patch.clv === 2.0)).toBe(true);
    expect(mockState.updated[0].patch.closing_line_id).toBe('cl-1');
  });

  test('falls back to player_name when player_id missing', async () => {
    mockState.grade = { id: 'g3', game_id: 'gm', sport: 'nba', stat_type: 'points', line: 25.5, direction: 'under', player_id: null, player_name: 'Test' };
    mockState.closing = { id: 'cl-2', pinnacle_line: 23.5 };
    const result = await clv.computeCLV('g3');
    expect(result.clv).toBe(2.0);
  });
});

describe('batchComputeCLV', () => {
  test('returns one entry per id, continues on individual failures', async () => {
    mockState.grade = { id: 'gx', game_id: 'gm', sport: 'nba', stat_type: 'points', line: 25.5, direction: 'over', player_id: '1' };
    mockState.closing = { id: 'cl-x', pinnacle_line: 26.5 };
    const out = await clv.batchComputeCLV(['gx', 'gx', 'gx']);
    expect(out).toHaveLength(3);
    expect(out[0].clv).toBe(1.0);
  });
});
