const request = require('supertest');

// Mock Redis
const mockRedis = {
  get: jest.fn(),
  set: jest.fn(),
  hset: jest.fn(),
  hgetall: jest.fn(),
  expire: jest.fn(),
};
jest.mock('../../src/utils/redis', () => ({
  getRedisClient: () => mockRedis,
}));

// Mock Supabase
const mockSupabaseFrom = jest.fn();
const mockSupabaseAuth = {
  getUser: jest.fn(),
};
jest.mock('../../src/utils/supabase', () => ({
  getSupabaseClient: () => ({ auth: mockSupabaseAuth, from: mockSupabaseFrom }),
  getSupabaseServiceClient: () => ({ auth: mockSupabaseAuth, from: mockSupabaseFrom }),
}));

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

process.env.ODDS_API_KEY = 'test-key';
process.env.NBA_SERVICE_URL = 'http://localhost:8000';

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

// --- Mock Data ---
const MOCK_USER_FREE = {
  id: 'user-free-1',
  email: 'free@test.com',
  tier: 'free',
  scan_count: 0,
  scan_reset_date: '2026-04-01',
  founder_status: false,
};

const MOCK_USER_PAID = {
  id: 'user-paid-1',
  email: 'paid@test.com',
  tier: 'analyst',
  scan_count: 100,
  scan_reset_date: '2026-04-01',
  founder_status: true,
};

const MOCK_ODDS_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_ODDS_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: 'spreads', last_update: '2026-03-21T14:28:00Z',
          outcomes: [
            { name: 'Denver Nuggets', price: -110, point: -5.5 },
            { name: 'Los Angeles Lakers', price: -110, point: 5.5 },
          ],
        },
      ],
    },
  ],
};

const MOCK_SEASON_AVG = {
  player: 'Nikola Jokic', player_id: 203999, team: 'DEN', season: '2025-26', source: 'cache',
  stats: { points: 26.3, rebounds: 12.4, assists: 9.1, threes: 1.1, blocks: 0.7, steals: 1.4, pra: 47.8, turnovers: 3.2, games_played: 65, minutes: 34.2 },
};

const MOCK_LAST_N = {
  player: 'Nikola Jokic', player_id: 203999, team: 'DEN', last_n: 10, source: 'cache',
  stats: { points: 28.1, rebounds: 13.0, assists: 10.2, threes: 1.3, blocks: 0.8, steals: 1.5, pra: 51.3, turnovers: 2.9, games_played: 10, minutes: 35.1 },
};

const MOCK_SPLITS = {
  splits: { home: { avg: 27.8, games: 33 }, away: { avg: 24.9, games: 32 } },
};
const MOCK_REST = {
  splits: { b2b: { avg: 23.1, games: 8 }, '1_day_rest': { avg: 26.5, games: 40 }, '2_plus_days_rest': { avg: 28.2, games: 17 } },
};
const MOCK_VS_TEAM = {
  splits: { vs_opponent: { avg: 30.5, games: 3 }, vs_all_others: { avg: 25.8, games: 62 } },
};

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

function setupAllMocks(user = MOCK_USER_FREE) {
  // Auth
  mockSupabaseAuth.getUser.mockResolvedValue({
    data: { user: { id: user.id, email: user.email } },
    error: null,
  });

  // Supabase from() calls
  mockSupabaseFrom.mockImplementation((table) => {
    if (table === 'users') {
      return {
        select: () => ({
          eq: () => ({
            single: () => Promise.resolve({ data: user, error: null }),
          }),
        }),
        update: () => ({
          eq: (col, val) => ({
            eq: () => ({
              select: () => ({
                single: () => Promise.resolve({ data: { scan_count: user.scan_count + 1 }, error: null }),
              }),
            }),
          }),
        }),
      };
    }
    if (table === 'picks') {
      return {
        insert: () => ({
          select: () => ({
            single: () => Promise.resolve({ data: { id: 'pick-' + Math.random().toString(36).slice(2) }, error: null }),
          }),
        }),
        select: () => ({
          eq: () => ({
            order: () => ({
              limit: () => Promise.resolve({ data: [], error: null }),
            }),
          }),
        }),
      };
    }
    if (table === 'scan_sessions') {
      return {
        insert: () => ({
          select: () => ({
            single: () => Promise.resolve({ data: { id: 'session-1' }, error: null }),
          }),
        }),
        select: () => ({
          eq: () => ({
            order: () => ({
              limit: () => Promise.resolve({ data: [], error: null }),
            }),
          }),
        }),
      };
    }
    // Default for other tables (e.g., scan history queries in upgradePitch)
    return {
      select: () => ({
        eq: () => ({
          order: () => ({
            limit: () => Promise.resolve({ data: [], error: null }),
          }),
          single: () => Promise.resolve({ data: null, error: null }),
        }),
      }),
    };
  });

  // Redis
  mockRedis.get.mockResolvedValue(null);
  mockRedis.set.mockResolvedValue('OK');
  mockRedis.hset.mockResolvedValue(1);
  mockRedis.hgetall.mockResolvedValue({});
  mockRedis.expire.mockResolvedValue(1);

  // Axios (Odds API + NBA stats service)
  axios.get.mockImplementation((url, config) => {
    if (url.includes('the-odds-api.com') && !url.includes('/odds')) {
      return Promise.resolve({ data: MOCK_ODDS_EVENTS, headers: API_HEADERS });
    }
    if (url.includes('the-odds-api.com') && url.includes('/odds')) {
      return Promise.resolve({ data: MOCK_ODDS_RESPONSE, headers: API_HEADERS });
    }
    if (url.includes('localhost:8000/stats/season-avg')) {
      return Promise.resolve({ data: MOCK_SEASON_AVG });
    }
    if (url.includes('localhost:8000/stats/last-n')) {
      return Promise.resolve({ data: MOCK_LAST_N });
    }
    if (url.includes('localhost:8000/stats/splits')) {
      const st = config?.params?.split_type;
      if (st === 'rest_days') return Promise.resolve({ data: MOCK_REST });
      if (st === 'vs_team') return Promise.resolve({ data: MOCK_VS_TEAM });
      return Promise.resolve({ data: MOCK_SPLITS });
    }
    return Promise.reject(new Error(`Unmocked: ${url}`));
  });
}

const VALID_PARLAY = {
  legs: [
    { player: 'Nikola Jokic', stat_type: 'points', line: 26.5, direction: 'over', book: 'draftkings' },
    { player: 'LeBron James', stat_type: 'rebounds', line: 8.5, direction: 'over', book: 'fanduel' },
  ],
};

// Session 7h: scan-limit middleware mounts on /api/scan/parlay. Reset
// between tests so the per-user 24h quota doesn't bleed across cases.
const { __internals: scanLimitInternals } = require('../../src/middleware/scanLimit');

beforeEach(() => {
  jest.clearAllMocks();
  scanLimitInternals.resetForTests();
});

describe('POST /api/scan/parlay', () => {
  test('returns 401 without auth token', async () => {
    const res = await request(app)
      .post('/api/scan/parlay')
      .send(VALID_PARLAY)
      .expect(401);
    expect(res.body.error).toContain('Authentication required');
  });

  test('returns 401 with invalid token', async () => {
    mockSupabaseAuth.getUser.mockResolvedValue({ data: { user: null }, error: { message: 'invalid' } });
    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer bad-token')
      .send(VALID_PARLAY)
      .expect(401);
    expect(res.body.error).toContain('Invalid');
  });

  test('returns 400 for fewer than 2 legs', async () => {
    setupAllMocks();
    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send({ legs: [{ player: 'Jokic', stat_type: 'points', line: 26.5, direction: 'over' }] })
      .expect(400);
    expect(res.body.error).toContain('at least 2');
  });

  test('returns 422 for more than 12 legs', async () => {
    setupAllMocks();
    const legs = Array(13).fill({ player: 'Jokic', stat_type: 'points', line: 26.5, direction: 'over' });
    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send({ legs })
      .expect(422);
    expect(res.body.error).toContain('12 legs');
  });

  test('full scan: returns complete response with grades + correlations', async () => {
    setupAllMocks();
    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send(VALID_PARLAY)
      .expect(200);

    expect(res.body.scan_id).toBeDefined();
    expect(res.body.parlay_grade).toMatch(/^[ABCD]$/);
    expect(typeof res.body.parlay_confidence).toBe('number');
    expect(Array.isArray(res.body.correlation_flags)).toBe(true);
    expect(Array.isArray(res.body.legs)).toBe(true);
    expect(res.body.legs).toHaveLength(2);
    expect(res.body.legs[0]).toHaveProperty('grade');
    expect(res.body.legs[0]).toHaveProperty('confidence');
    expect(res.body.legs[0]).toHaveProperty('edge_pct');
    expect(res.body.legs[0]).toHaveProperty('reasoning_summary');
    expect(typeof res.body.scan_count).toBe('number');
  });

  test('paid user: unlimited scans, no pitch', async () => {
    setupAllMocks(MOCK_USER_PAID);
    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send(VALID_PARLAY)
      .expect(200);

    expect(res.body.upgrade_pitch).toBeNull();
    expect(res.body.scans_remaining).toBeNull();
  });

  test('scan 5: returns analysis WITH upgrade pitch', async () => {
    const user5 = { ...MOCK_USER_FREE, scan_count: 4 };
    setupAllMocks(user5);

    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send(VALID_PARLAY)
      .expect(200);

    expect(res.body.upgrade_pitch).not.toBeNull();
    expect(res.body.upgrade_pitch.hook).toBeDefined();
    expect(res.body.upgrade_pitch.cta).toContain('founder rate');
    expect(res.body.upgrade_pitch.tier_recommended).toBeDefined();
    expect(res.body.scans_remaining).toBe(0);
  });

  test('scan 6+: returns 403 with upgrade pitch, no analysis', async () => {
    const user6 = { ...MOCK_USER_FREE, scan_count: 5 };
    setupAllMocks(user6);

    const res = await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send(VALID_PARLAY)
      .expect(403);

    expect(res.body.error).toBe('scan_limit_reached');
    expect(res.body.upgrade_pitch).toBeDefined();
    expect(res.body.upgrade_pitch.cta).toContain('founder rate');
    // No legs in the response since analysis was blocked
    expect(res.body.legs).toBeUndefined();
  });

  test('database: picks and scan_sessions are written', async () => {
    setupAllMocks();

    await request(app)
      .post('/api/scan/parlay')
      .set('Authorization', 'Bearer valid-token')
      .send(VALID_PARLAY)
      .expect(200);

    // Verify picks were inserted. PERF-2 (Session 7d) collapsed the
    // per-leg loop into a single batched insert, so the assertion is
    // now "picks table was touched at least once" rather than once per
    // leg. The batched call's payload would contain both leg rows.
    const pickInserts = mockSupabaseFrom.mock.calls.filter(([t]) => t === 'picks');
    expect(pickInserts.length).toBeGreaterThanOrEqual(1);

    // Verify scan_sessions insert was called once
    const sessionInserts = mockSupabaseFrom.mock.calls.filter(([t]) => t === 'scan_sessions');
    expect(sessionInserts.length).toBeGreaterThanOrEqual(1);
  });
});
