// Tank01 daily prefetch (Session 15) — orchestrator that pulls the
// Redis cache keys session 14's augmentor reads. Tests cover:
//   - graceful skip when adapter has no API key
//   - budget cap respected
//   - dry-run suppresses adapter calls
//   - final-status box scores get pulled, non-final skipped
//   - empty slate doesn't crash

const mockNbaGames = jest.fn();
const mockNbaBox = jest.fn();
const mockNbaOdds = jest.fn();
const mockNbaHasKey = jest.fn(() => true);
jest.mock('../../src/services/adapters/tank01NbaAdapter', () => ({
  getNBAGamesForDate: (...a) => mockNbaGames(...a),
  getNBABoxScore: (...a) => mockNbaBox(...a),
  getNBABettingOdds: (...a) => mockNbaOdds(...a),
  hasApiKey: (...a) => mockNbaHasKey(...a),
}));

const mockMlbSlate = jest.fn();
const mockMlbBox = jest.fn();
const mockMlbBvp = jest.fn();
const mockMlbHasKey = jest.fn(() => true);
jest.mock('../../src/services/adapters/tank01MlbAdapter', () => ({
  getMLBDailyScoreboard: (...a) => mockMlbSlate(...a),
  getMLBBoxScore: (...a) => mockMlbBox(...a),
  getMLBBatterVsPitcher: (...a) => mockMlbBvp(...a),
  hasApiKey: (...a) => mockMlbHasKey(...a),
}));

const prefetch = require('../../scripts/tank01-prefetch');

beforeEach(() => {
  mockNbaGames.mockReset();
  mockNbaBox.mockReset();
  mockNbaOdds.mockReset();
  mockNbaHasKey.mockReset().mockReturnValue(true);
  mockMlbSlate.mockReset();
  mockMlbBox.mockReset();
  mockMlbBvp.mockReset();
  mockMlbHasKey.mockReset().mockReturnValue(true);
});

describe('parseArgs', () => {
  test('defaults', () => {
    const a = prefetch.__internals.parseArgs(['node', 'script']);
    expect(a.maxRequests).toBe(prefetch.__internals.DEFAULT_BUDGET);
    expect(a.dryRun).toBe(false);
    expect(a.sports).toEqual(['nba', 'mlb']);
  });
  test('--max=N', () => {
    expect(prefetch.__internals.parseArgs(['node', 's', '--max=25']).maxRequests).toBe(25);
  });
  test('--max ignores non-numeric / non-positive', () => {
    expect(prefetch.__internals.parseArgs(['node', 's', '--max=0']).maxRequests).toBe(80);
    expect(prefetch.__internals.parseArgs(['node', 's', '--max=foo']).maxRequests).toBe(80);
  });
  test('--dry-run', () => {
    expect(prefetch.__internals.parseArgs(['node', 's', '--dry-run']).dryRun).toBe(true);
  });
  test('--sports filter', () => {
    expect(prefetch.__internals.parseArgs(['node', 's', '--sports=mlb']).sports).toEqual(['mlb']);
  });
});

describe('budget tracker', () => {
  test('counts spend, refuses past cap', () => {
    const b = prefetch.__internals.makeBudget(3);
    expect(b.canSpend()).toBe(true);
    b.spend(); b.spend(); b.spend();
    expect(b.canSpend()).toBe(false);
    expect(b.spent()).toBe(3);
  });
});

describe('main — NBA path', () => {
  test('skips entirely when adapter has no API key', async () => {
    mockNbaHasKey.mockReturnValueOnce(false);
    mockMlbHasKey.mockReturnValueOnce(false);
    const r = await prefetch.main(['node', 'script', '--sports=nba']);
    expect(r.nba.skipped).toBe('no_key');
    expect(mockNbaGames).not.toHaveBeenCalled();
  });

  test('pulls slate, then box score per FINAL game, plus odds once', async () => {
    mockNbaGames.mockResolvedValueOnce([
      { gameId: 'G1', gameStatus: 'Final' },
      { gameId: 'G2', gameStatus: 'InProgress' },  // skip
      { gameId: 'G3', gameStatus: 'Final' },
    ]);
    mockNbaBox.mockResolvedValue({});
    mockNbaOdds.mockResolvedValueOnce({});
    const r = await prefetch.main(['node', 'script', '--sports=nba']);
    expect(r.nba.games).toBe(3);
    expect(r.nba.boxscores).toBe(2);   // only the Finals
    expect(r.nba.odds).toBe(true);
    expect(mockNbaBox).toHaveBeenCalledTimes(2);
  });

  test('budget cap stops fetches mid-loop', async () => {
    mockNbaGames.mockResolvedValueOnce(
      Array.from({ length: 10 }, (_, i) => ({ gameId: `G${i}`, gameStatus: 'Final' })),
    );
    mockNbaBox.mockResolvedValue({});
    mockNbaOdds.mockResolvedValueOnce({});
    // Budget = 4: one for getNBAGamesForDate, leaves 3 box-score
    // slots (no odds — budget exhausted first).
    const r = await prefetch.main(['node', 'script', '--sports=nba', '--max=4']);
    expect(mockNbaGames).toHaveBeenCalledTimes(1);
    expect(mockNbaBox).toHaveBeenCalledTimes(3);
    expect(mockNbaOdds).not.toHaveBeenCalled();
    expect(r.requestsSpent).toBe(4);
  });

  test('dry-run skips ALL adapter calls', async () => {
    const r = await prefetch.main(['node', 'script', '--dry-run']);
    expect(mockNbaGames).not.toHaveBeenCalled();
    expect(mockNbaBox).not.toHaveBeenCalled();
    expect(mockNbaOdds).not.toHaveBeenCalled();
    expect(r.nba.skipped).toBe('dry_run');
    expect(r.mlb.skipped).toBe('dry_run');
  });

  test('empty slate is not an error', async () => {
    mockNbaGames.mockResolvedValueOnce([]);
    mockNbaOdds.mockResolvedValueOnce({});
    const r = await prefetch.main(['node', 'script', '--sports=nba']);
    expect(r.nba.games).toBe(0);
    expect(r.nba.boxscores).toBe(0);
    // Odds still pulled — it's a single daily call, not per-game.
    expect(r.nba.odds).toBe(true);
  });

  test('null slate (adapter returned null) is not an error', async () => {
    mockNbaGames.mockResolvedValueOnce(null);
    const r = await prefetch.main(['node', 'script', '--sports=nba']);
    expect(r.nba.games).toBe(0);
    expect(r.nba.boxscores).toBe(0);
  });
});

describe('main — MLB path', () => {
  test('skips when adapter has no API key', async () => {
    mockMlbHasKey.mockReturnValueOnce(false);
    const r = await prefetch.main(['node', 'script', '--sports=mlb']);
    expect(r.mlb.skipped).toBe('no_key');
  });

  test('pulls scoreboard + box scores for Finals/Completed', async () => {
    mockMlbSlate.mockResolvedValueOnce([
      { gameId: 'M1', gameStatus: 'Final' },
      { gameId: 'M2', gameStatus: 'In Progress' },  // skip
      { gameId: 'M3', gameStatus: 'Completed' },
    ]);
    mockMlbBox.mockResolvedValue({});
    const r = await prefetch.main(['node', 'script', '--sports=mlb']);
    expect(r.mlb.games).toBe(3);
    expect(r.mlb.boxscores).toBe(2);
    expect(r.mlb.bvp_skipped_reason).toMatch(/scoreboard/i);
  });
});
