process.env.ODDSPAPI_KEY = 'test-key';
process.env.ODDSPAPI_BASE_URL = 'https://api.oddspapi.test/v1';

const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({
  get: (...args) => mockAxiosGet(...args),
}));

// Fluent supabase fake. Records every upsert; from('grade_history') returns
// configurable rows; from('closing_lines') accepts upserts.
const mockGradeRows = { current: [] };
const mockUpserts = { current: [] };
jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({
    from(table) {
      const ctx = { table };
      const proxy = {
        select() { return proxy; },
        eq() { return proxy; },
        is() { return Promise.resolve({ data: mockGradeRows.current, error: null }); },
        upsert(row) {
          mockUpserts.current.push({ table, row });
          return Promise.resolve({ error: null });
        },
      };
      return proxy;
    },
  }),
}));

const adapter = require('../../src/services/adapters/oddsPapiAdapter');

beforeEach(() => {
  mockAxiosGet.mockReset();
  mockGradeRows.current = [];
  mockUpserts.current = [];
});

describe('oddsPapiAdapter.configured', () => {
  test('reflects ODDSPAPI_KEY presence', () => {
    expect(adapter.configured()).toBe(true);
    delete process.env.ODDSPAPI_KEY;
    expect(adapter.configured()).toBe(false);
    process.env.ODDSPAPI_KEY = 'test-key';
  });
});

describe('getPinnacleClosingLine', () => {
  test('devigs Pinnacle prop into fair probabilities', async () => {
    mockAxiosGet.mockResolvedValue({
      data: {
        props: [
          { player: 'Jalen Brunson', stat_type: 'points', line: 26.5, over_price: -110, under_price: -110 },
        ],
      },
    });
    const closing = await adapter.getPinnacleClosingLine('nba', 'g-1', '3934672', 'points', 'Jalen Brunson');
    expect(closing).toMatchObject({ line: 26.5, overOdds: -110, underOdds: -110 });
    expect(closing.fairOver).toBeCloseTo(0.5, 5);
    expect(typeof closing.capturedAt).toBe('string');
  });

  test('returns null when no matching prop', async () => {
    mockAxiosGet.mockResolvedValue({ data: { props: [] } });
    const result = await adapter.getPinnacleClosingLine('nba', 'g-1', '3934672', 'points', 'Ghost');
    expect(result).toBeNull();
  });

  test('returns null when unconfigured', async () => {
    delete process.env.ODDSPAPI_KEY;
    const result = await adapter.getPinnacleClosingLine('nba', 'g-1', 'x', 'points', 'Anyone');
    expect(result).toBeNull();
    process.env.ODDSPAPI_KEY = 'test-key';
  });
});

describe('batchCapture', () => {
  test('skips when no graded props exist for the game', async () => {
    mockGradeRows.current = [];
    const summary = await adapter.batchCapture('nba', 'empty-game');
    expect(summary).toMatchObject({ captured: 0, reason: 'no_graded_props' });
    expect(mockUpserts.current).toHaveLength(0);
  });

  test('upserts closing_lines for each unique (player, stat) pair', async () => {
    mockGradeRows.current = [
      { player_id: '1', player_name: 'A. Player', stat_type: 'points' },
      { player_id: '1', player_name: 'A. Player', stat_type: 'points' },  // duplicate
      { player_id: '2', player_name: 'B. Player', stat_type: 'rebounds' },
    ];
    mockAxiosGet.mockResolvedValue({
      data: {
        props: [
          { player: 'A. Player', stat_type: 'points', line: 22.5, over_price: -110, under_price: -110 },
          { player: 'B. Player', stat_type: 'rebounds', line: 8.5, over_price: -105, under_price: -115 },
        ],
      },
    });
    const summary = await adapter.batchCapture('nba', 'multi-game');
    expect(summary.captured).toBe(2);
    expect(summary.total).toBe(2); // duplicates collapsed
    expect(mockUpserts.current).toHaveLength(2);
    expect(mockUpserts.current[0].table).toBe('closing_lines');
    expect(mockUpserts.current[0].row).toMatchObject({
      game_id: 'multi-game',
      sport: 'nba',
      stat_type: 'points',
      pinnacle_line: 22.5,
    });
  });

  test('marks props skipped when Pinnacle has no line for them', async () => {
    mockGradeRows.current = [{ player_id: '99', player_name: 'No Line', stat_type: 'points' }];
    mockAxiosGet.mockResolvedValue({ data: { props: [] } });
    const summary = await adapter.batchCapture('nba', 'no-pin');
    expect(summary).toMatchObject({ captured: 0, skipped: 1, total: 1 });
  });
});
