const request = require('supertest');

// Mock Redis before importing app
const mockRedis = {
  get: jest.fn(),
  set: jest.fn(),
  hset: jest.fn(),
  hgetall: jest.fn(),
  expire: jest.fn(),
};
jest.mock('../../src/utils/redis', () => ({
  getRedisClient: () => mockRedis,
  // Session 20 — provider gateway pulls cacheGet/cacheSet/isDegraded.
  // Degraded mode lets every call through and skips Redis writes, so
  // the existing axios-mock assertions stay accurate.
  cacheGet: jest.fn(async () => null),
  cacheSet: jest.fn(async () => true),
  cacheDel: jest.fn(async () => true),
  isDegraded: jest.fn(() => true),
}));

// Mock axios
jest.mock('axios');
const axios = require('axios');

// Set env
process.env.ODDS_API_KEY = 'test-key';

const app = require('../../src/app');

const MOCK_EVENTS = [
  {
    id: 'game-1',
    sport_key: 'basketball_nba',
    home_team: 'Denver Nuggets',
    away_team: 'Los Angeles Lakers',
    commence_time: '2026-03-21T19:00:00Z',
  },
];

const MOCK_ODDS_RESPONSE = {
  ...MOCK_EVENTS[0],
  bookmakers: [
    {
      key: 'draftkings',
      title: 'DraftKings',
      markets: [
        {
          key: 'player_points',
          last_update: '2026-03-21T14:28:00Z',
          outcomes: [
            { name: 'Over', description: 'Nikola Jokic', price: -110, point: 26.5 },
            { name: 'Under', description: 'Nikola Jokic', price: -110, point: 26.5 },
            { name: 'Over', description: 'LeBron James', price: -115, point: 25.5 },
            { name: 'Under', description: 'LeBron James', price: -105, point: 25.5 },
          ],
        },
        {
          key: 'player_rebounds',
          last_update: '2026-03-21T14:28:00Z',
          outcomes: [
            { name: 'Over', description: 'Nikola Jokic', price: -130, point: 12.5 },
            { name: 'Under', description: 'Nikola Jokic', price: 110, point: 12.5 },
          ],
        },
      ],
    },
    {
      key: 'fanduel',
      title: 'FanDuel',
      markets: [
        {
          key: 'player_points',
          last_update: '2026-03-21T14:30:00Z',
          outcomes: [
            { name: 'Over', description: 'Nikola Jokic', price: -105, point: 27.0 },
            { name: 'Under', description: 'Nikola Jokic', price: -115, point: 27.0 },
          ],
        },
      ],
    },
  ],
};

const API_HEADERS = {
  'x-requests-remaining': '488',
  'x-requests-used': '12',
  'x-requests-last': '1',
};

function setupMockApi() {
  axios.get
    .mockResolvedValueOnce({ data: MOCK_EVENTS, headers: API_HEADERS })
    .mockResolvedValueOnce({ data: MOCK_ODDS_RESPONSE, headers: API_HEADERS });
}

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

describe('GET /api/odds/nba', () => {
  it('returns full response with correct shape on live fetch', async () => {
    setupMockApi();

    const res = await request(app).get('/api/odds/nba').expect(200);

    expect(res.body.sport).toBe('nba');
    expect(res.body.source).toBe('live');
    expect(res.body.updated_at).toBeDefined();
    expect(res.body.quota_remaining).toBe(488);
    expect(Array.isArray(res.body.props)).toBe(true);
    expect(res.body.props.length).toBeGreaterThan(0);

    // Check grouped structure
    const jokicPoints = res.body.props.find(
      (p) => p.player === 'Nikola Jokic' && p.stat_type === 'points'
    );
    expect(jokicPoints).toBeDefined();
    expect(jokicPoints.home_team).toBe('DEN');
    expect(jokicPoints.away_team).toBe('LAL');
    expect(jokicPoints.lines.length).toBe(2); // draftkings + fanduel
    expect(jokicPoints.lines[0]).toHaveProperty('book');
    expect(jokicPoints.lines[0]).toHaveProperty('line');
    expect(jokicPoints.lines[0]).toHaveProperty('over_odds');
    expect(jokicPoints.lines[0]).toHaveProperty('under_odds');
    expect(jokicPoints.lines[0]).toHaveProperty('fetched_at');
  });

  it('serves cached data on second request within 15 minutes', async () => {
    const cachedData = {
      updated_at: '2026-03-21T14:00:00Z',
      props: [
        {
          player: 'Nikola Jokic',
          home_team: 'DEN',
          away_team: 'LAL',
          game_time: '2026-03-21T19:00:00Z',
          stat_type: 'points',
          book: 'draftkings',
          line: 26.5,
          over_odds: -110,
          under_odds: -110,
          fetched_at: '2026-03-21T14:28:00Z',
        },
      ],
    };
    mockRedis.get.mockResolvedValue(JSON.stringify(cachedData));

    const res = await request(app).get('/api/odds/nba').expect(200);

    expect(res.body.source).toBe('cache');
    expect(axios.get).not.toHaveBeenCalled();
  });

  it('filters by stat_type', async () => {
    setupMockApi();

    const res = await request(app).get('/api/odds/nba?stat_type=rebounds').expect(200);

    for (const prop of res.body.props) {
      expect(prop.stat_type).toBe('rebounds');
    }
  });

  it('filters by player (partial match, case-insensitive)', async () => {
    setupMockApi();

    const res = await request(app).get('/api/odds/nba?player=jokic').expect(200);

    for (const prop of res.body.props) {
      expect(prop.player.toLowerCase()).toContain('jokic');
    }
  });

  it('filters by book', async () => {
    setupMockApi();

    const res = await request(app).get('/api/odds/nba?book=fanduel').expect(200);

    for (const prop of res.body.props) {
      for (const line of prop.lines) {
        expect(line.book).toBe('fanduel');
      }
    }
  });

  it('returns 400 for invalid stat_type', async () => {
    const res = await request(app).get('/api/odds/nba?stat_type=invalid').expect(400);
    expect(res.body.error).toContain('Invalid stat_type');
  });

  it('returns 400 for invalid book', async () => {
    const res = await request(app).get('/api/odds/nba?book=bovada').expect(400);
    expect(res.body.error).toContain('Invalid book');
  });

  it('returns stale cache data when API fails', async () => {
    mockRedis.get
      .mockResolvedValueOnce(null) // cache miss
      .mockResolvedValueOnce(JSON.stringify({ // stale fallback
        updated_at: '2026-03-21T12:00:00Z',
        props: [{ player: 'Stale Data', home_team: 'DEN', away_team: 'LAL', game_time: '2026-03-21T19:00:00Z', stat_type: 'points', book: 'draftkings', line: 26.5, over_odds: -110, under_odds: -110, fetched_at: '2026-03-21T12:00:00Z' }],
      }));

    axios.get.mockRejectedValue(new Error('API down'));

    const res = await request(app).get('/api/odds/nba').expect(200);

    expect(res.body.source).toBe('cache');
    expect(res.headers['x-vyndr-stale']).toBe('true');
  });

  it('returns 503 when API fails and no cache', async () => {
    mockRedis.get.mockResolvedValue(null);
    axios.get.mockRejectedValue(new Error('API down'));

    const res = await request(app).get('/api/odds/nba').expect(503);
    expect(res.body.error).toBe('Odds service unavailable.');
  });
});

describe('GET /api/odds/ncaab', () => {
  it('returns off-season message when NCAAB not in season', async () => {
    // Mock Date to July (off-season)
    const realDate = Date;
    const mockDate = new Date('2026-07-15T12:00:00Z');
    jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
    Date.now = realDate.now;

    const res = await request(app).get('/api/odds/ncaab').expect(200);

    expect(res.body.props).toEqual([]);
    expect(res.body.message).toContain('off-season');
    expect(res.body.source).toBe('none');

    jest.restoreAllMocks();
  });
});
