// PERF-2 (Session 7d): proves analyzeProp runs in parallel inside
// scanParlay. ARCH-1 Step 4 (Session 7f): the call target rotated from
// `propAnalyzer.analyzeProp` to `intelligence/analyzeViaEngine1`. Mock
// target updated to follow; the assertion (parallel timing) is
// unchanged.

let mockAnalyzeDelayMs = 100;
let mockAnalyzeCallTimes = [];
let mockAnalyzeRejectIndices = new Set();

jest.mock('../../src/services/intelligence/analyzeViaEngine1', () => ({
  analyzeViaEngine1: async (leg) => {
    const startedAt = Date.now();
    mockAnalyzeCallTimes.push(startedAt);
    await new Promise((r) => setTimeout(r, mockAnalyzeDelayMs));
    if (mockAnalyzeRejectIndices.has(leg._idx)) {
      throw new Error(`forced failure for leg ${leg._idx}`);
    }
    return {
      ...leg,
      // Mock keeps the legacy-shape 4-letter grade so the existing
      // value-level assertion ('A-') is preserved verbatim. Real
      // analyzeViaEngine1 also emits the 4-letter shape via the adapter.
      grade: 'A-',
      confidence: 78,
      edge_pct: 5.2,
      reasoning: { summary: 'ok', steps: {} },
      kill_conditions_triggered: [],
    };
  },
}));

jest.mock('../../src/services/oddsService', () => ({
  getOdds: async () => ({ spreads: [], props: [] }),
}));

jest.mock('../../src/services/correlationEngine', () => ({
  detectCorrelations: () => [],
}));

jest.mock('../../src/services/parlayGrader', () => ({
  gradeParlayFromLegs: () => ({ grade: 'A-', confidence: 0.7 }),
}));

jest.mock('../../src/services/upgradePitch', () => ({
  generateUpgradePitch: async () => null,
}));

function makeSelectChain(arrayRows, singleRow) {
  return {
    single: () => Promise.resolve({ data: singleRow, error: null }),
    then: (resolve) => resolve({ data: arrayRows, error: null }),
  };
}

const mockSupabase = {
  from() {
    return {
      insert(rows) {
        const isArr = Array.isArray(rows);
        const arrayRows = isArr ? rows.map((_, i) => ({ id: `p${i + 1}` })) : [{ id: 'p1' }];
        const singleRow = { id: 'sess-1' };
        return {
          select: () => makeSelectChain(arrayRows, singleRow),
        };
      },
      update() {
        const chain = {
          eq() { return chain; },
          select() { return chain; },
          single: () => Promise.resolve({ data: { scan_count: 1 }, error: null }),
        };
        return chain;
      },
    };
  },
};
jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => mockSupabase,
}));

const { scanParlay } = require('../../src/services/parlayScanService');

beforeEach(() => {
  mockAnalyzeCallTimes = [];
  mockAnalyzeRejectIndices = new Set();
  mockAnalyzeDelayMs = 80;
});

describe('parlayScanService parallel leg resolution (PERF-2)', () => {
  test('6 legs resolve in roughly one delay window, not six', async () => {
    const user = { id: 'u1', tier: 'desk', scan_count: 0 };
    const legs = [0, 1, 2, 3, 4, 5].map((i) => ({
      _idx: i, player: `P${i}`, stat_type: 'points', line: 25, direction: 'over',
    }));
    const start = Date.now();
    const out = await scanParlay(user, legs);
    const elapsed = Date.now() - start;

    expect(out.legs).toHaveLength(6);
    // analyzeProp was invoked once per leg.
    expect(mockAnalyzeCallTimes.length).toBe(6);
    // Every call started within a small window of each other — proves
    // the loop didn't await one before issuing the next.
    const first = Math.min(...mockAnalyzeCallTimes);
    const last = Math.max(...mockAnalyzeCallTimes);
    expect(last - first).toBeLessThan(40);

    // Sequential 6 × 80ms ≈ 480ms; parallel should land near 80-200ms
    // depending on host. Leave generous headroom for slow CI.
    expect(elapsed).toBeLessThan(6 * mockAnalyzeDelayMs * 0.7);
  });

  test('one failed leg does not crash the parlay; the rest succeed', async () => {
    const user = { id: 'u2', tier: 'desk', scan_count: 0 };
    const legs = [0, 1, 2].map((i) => ({
      _idx: i, player: `P${i}`, stat_type: 'points', line: 25, direction: 'over',
    }));
    mockAnalyzeRejectIndices = new Set([1]);
    const out = await scanParlay(user, legs);
    expect(out.legs).toHaveLength(3);
    // Index 1 is the failed leg — grade should be 'F' from the stub.
    expect(out.legs[1].grade).toBe('F');
    expect(out.legs[0].grade).toBe('A-');
    expect(out.legs[2].grade).toBe('A-');
  });
});
