// Session 16 — sport-specific market scoping in oddsService.
// Replaces a runtime axios interceptor (NODE_OPTIONS --require) that
// had been deployed to filter out cross-sport markets the upstream
// odds-api 422s on. These tests pin the contract so the runtime hack
// can be retired safely.

const { SPORT_MARKETS, getMarketsForSport } = require('../../src/services/oddsService');

describe('SPORT_MARKETS — isolation', () => {
  test('NBA market list contains no soccer markets', () => {
    expect(SPORT_MARKETS.nba).not.toMatch(/player_goals\b/);
    expect(SPORT_MARKETS.nba).not.toMatch(/player_shots_on_target/);
    expect(SPORT_MARKETS.nba).not.toMatch(/player_tackles/);
    expect(SPORT_MARKETS.nba).not.toMatch(/player_cards/);
    expect(SPORT_MARKETS.nba).not.toMatch(/team_clean_sheet/);
  });

  test('NBA market list contains no MLB markets', () => {
    expect(SPORT_MARKETS.nba).not.toMatch(/batter_/);
    expect(SPORT_MARKETS.nba).not.toMatch(/pitcher_/);
  });

  test('NBA market list does contain canonical NBA markets', () => {
    expect(SPORT_MARKETS.nba).toMatch(/player_points/);
    expect(SPORT_MARKETS.nba).toMatch(/player_rebounds/);
    expect(SPORT_MARKETS.nba).toMatch(/player_assists/);
    expect(SPORT_MARKETS.nba).toMatch(/player_threes/);
    expect(SPORT_MARKETS.nba).toMatch(/spreads/);
  });

  test('WNBA market list is NBA-shaped minus PRA combo', () => {
    expect(SPORT_MARKETS.wnba).toMatch(/player_points/);
    expect(SPORT_MARKETS.wnba).toMatch(/player_rebounds/);
    // WNBA odds-api doesn't expose the PRA combo today.
    expect(SPORT_MARKETS.wnba).not.toMatch(/points_rebounds_assists/);
    expect(SPORT_MARKETS.wnba).not.toMatch(/batter_/);
    expect(SPORT_MARKETS.wnba).not.toMatch(/player_goals\b/);
  });

  test('MLB market list contains batter + pitcher markets, no basketball', () => {
    expect(SPORT_MARKETS.mlb).toMatch(/batter_home_runs/);
    expect(SPORT_MARKETS.mlb).toMatch(/batter_hits/);
    expect(SPORT_MARKETS.mlb).toMatch(/pitcher_strikeouts/);
    expect(SPORT_MARKETS.mlb).not.toMatch(/player_points/);
    expect(SPORT_MARKETS.mlb).not.toMatch(/player_goals\b/);
  });

  test('every soccer league shares the same market list', () => {
    const soccerKeys = Object.keys(SPORT_MARKETS).filter((k) => k.startsWith('soccer_'));
    expect(soccerKeys.length).toBeGreaterThanOrEqual(9);
    const first = SPORT_MARKETS[soccerKeys[0]];
    for (const k of soccerKeys) {
      expect(SPORT_MARKETS[k]).toBe(first);
    }
  });

  test('soccer market list contains soccer-only markets, no basketball/baseball', () => {
    const wc = SPORT_MARKETS.soccer_wc;
    expect(wc).toMatch(/player_goals/);
    expect(wc).toMatch(/player_shots_on_target/);
    expect(wc).toMatch(/player_cards/);
    expect(wc).toMatch(/team_clean_sheet/);
    expect(wc).not.toMatch(/player_points/);
    expect(wc).not.toMatch(/batter_/);
  });

  test('every PLAYER-PROP market list ends with `spreads`', () => {
    for (const [sport, list] of Object.entries(SPORT_MARKETS)) {
      // Wave 6 — MMA is a GAME-level sport (h2h + round totals only); it has
      // no player-prop `spreads` market and odds-api 422s if one is sent.
      if (sport === 'mma') continue;
      // We don't require spreads to be the literal final segment,
      // only that it's present in the comma-separated list.
      expect(list.split(',')).toContain('spreads');
    }
  });

  test('MMA market list is game-level (h2h + totals), no spreads/player props', () => {
    expect(SPORT_MARKETS.mma).toBe('h2h,totals');
    expect(SPORT_MARKETS.mma).not.toMatch(/spreads/);
    expect(SPORT_MARKETS.mma).not.toMatch(/player_/);
  });

  test('SPORT_MARKETS is frozen at the top level', () => {
    expect(Object.isFrozen(SPORT_MARKETS)).toBe(true);
  });
});

describe('getMarketsForSport', () => {
  test('returns the NBA list for nba', () => {
    expect(getMarketsForSport('nba')).toBe(SPORT_MARKETS.nba);
  });
  test('returns the soccer_wc list for soccer_wc', () => {
    expect(getMarketsForSport('soccer_wc')).toBe(SPORT_MARKETS.soccer_wc);
  });
  test('unknown sport falls back to NBA (safe default)', () => {
    expect(getMarketsForSport('cricket')).toBe(SPORT_MARKETS.nba);
  });
  test('null / undefined / empty fall back to NBA', () => {
    expect(getMarketsForSport(null)).toBe(SPORT_MARKETS.nba);
    expect(getMarketsForSport(undefined)).toBe(SPORT_MARKETS.nba);
    expect(getMarketsForSport('')).toBe(SPORT_MARKETS.nba);
  });
});

describe('fetchEventOddsFromApi uses sport-scoped markets', () => {
  // Mock axios so the test doesn't hit the network.
  jest.resetModules();
  const mockGet = jest.fn(() => Promise.resolve({ data: {}, headers: {} }));
  jest.doMock('axios', () => ({ get: mockGet }));
  const { fetchEventOddsFromApi, SPORT_MARKETS: SM } = require('../../src/services/oddsService');

  beforeEach(() => mockGet.mockClear());

  test('NBA fetch sends NBA markets only', async () => {
    await fetchEventOddsFromApi('basketball_nba', 'evt1', 'key', 'nba');
    const [, opts] = mockGet.mock.calls[0];
    expect(opts.params.markets).toBe(SM.nba);
  });

  test('MLB fetch sends MLB markets only', async () => {
    await fetchEventOddsFromApi('baseball_mlb', 'evt1', 'key', 'mlb');
    const [, opts] = mockGet.mock.calls[0];
    expect(opts.params.markets).toBe(SM.mlb);
  });

  test('soccer fetch sends soccer markets only', async () => {
    await fetchEventOddsFromApi('soccer_fifa_world_cup', 'evt1', 'key', 'soccer_wc');
    const [, opts] = mockGet.mock.calls[0];
    expect(opts.params.markets).toBe(SM.soccer_wc);
  });

  test('omitted sport arg falls back to NBA markets (legacy callers)', async () => {
    await fetchEventOddsFromApi('basketball_nba', 'evt1', 'key');
    const [, opts] = mockGet.mock.calls[0];
    expect(opts.params.markets).toBe(SM.nba);
  });
});
