'use strict';

// Model Train — value engine: takeable gate, value flag, and the end-to-end
// wiring in analyzeViaEngine1 (de-vig + EV + triplet + flags).

const ve = require('../../src/config/valueEngine');

describe('valueEngine config (steps 3-4)', () => {
  test('takeable band is -160..+200 by default', () => {
    expect(ve.isTakeable(-110)).toBe(true);
    expect(ve.isTakeable(-160)).toBe(true);   // ceiling inclusive
    expect(ve.isTakeable(+200)).toBe(true);   // max inclusive
    expect(ve.isTakeable(-200)).toBe(false);  // too chalky
    expect(ve.isTakeable(+250)).toBe(false);  // too long
    expect(ve.isTakeable(null)).toBe(false);
  });
  test('value = takeable AND ev above threshold', () => {
    expect(ve.isValue(-120, 5)).toBe(true);   // takeable + 5% EV
    expect(ve.isValue(-120, 1)).toBe(false);  // takeable but EV below 2%
    expect(ve.isValue(-900, 20)).toBe(false); // huge EV but not takeable
  });
});

// ── analyzeViaEngine1 value fields ──────────────────────────────────────────
const mockCompute = { current: null };
jest.mock('../../src/services/intelligence/computeFeatures', () => ({
  computeFeaturesForProp: async () => mockCompute.current,
}));
jest.mock('../../src/services/intelligence/engine1', () => ({
  gradeProp: () => ({ grade: 'B', confidence: 0.55, top_factors: [], all_factors: [] }),
}));
const mockPOver = { current: 0.6 };
jest.mock('../../src/services/intelligence/probabilityEstimator', () => ({
  estimateProbability: () => ({ p_over: mockPOver.current }),
}));
const { analyzeViaEngine1 } = require('../../src/services/intelligence/analyzeViaEngine1');

beforeEach(() => {
  mockCompute.current = {
    features: { l5_avg: 1.4, l20_avg: 1.3 }, trap: {}, consistency: { consistency: 'reliable', score: 0.7 },
    prop: { line: 0.5, direction: 'over' }, meta: { sport: 'mlb', gameLogs: [{ hits: 1 }], errors: [] },
  };
  mockPOver.current = 0.6; // model P(over) = 60%
});

describe('analyzeViaEngine1 — de-vig + EV + triplet (steps 1,2,6)', () => {
  test('computes the value triplet, EV, and flags from both-sided odds', async () => {
    // book: over -130 / under +110. model P(over) 0.60.
    const out = await analyzeViaEngine1({
      player: 'X', stat_type: 'hits', line: 0.5, direction: 'over', over_odds: -130, under_odds: 110,
    });
    expect(out.grade).toBe('B');            // still a real read
    expect(out.book_odds).toBe(-130);       // book price
    expect(typeof out.fair_odds).toBe('number'); // de-vigged fair price present (both sides)
    expect(out.model_odds).toBe(-150);      // impliedProbToAmerican(0.60)
    expect(out.p_win).toBe(0.6);
    // EV at -130 with p 0.60: 0.60*(1+100/130) - 1 = +6.15%
    expect(out.ev_pct).toBeGreaterThan(5);
    expect(out.takeable).toBe(true);        // -130 in band
    expect(out.value).toBe(true);           // takeable + EV > 2%
    expect(out.devig_method).toBe('multiplicative');
  });

  test('one-sided odds → fair UNAVAILABLE, but book price + EV still ship', async () => {
    const out = await analyzeViaEngine1({
      player: 'X', stat_type: 'hits', line: 0.5, direction: 'over', over_odds: -130, // no under_odds
    });
    expect(out.book_odds).toBe(-130);
    expect(out.fair_odds).toBeUndefined();  // never faked from one side
    expect(out.ev_pct).toBeGreaterThan(0);  // EV from model + actual price
    expect(out.takeable).toBe(true);
  });

  test('a chalky price is not takeable → not value even at positive EV', async () => {
    mockPOver.current = 0.92;
    const out = await analyzeViaEngine1({
      player: 'X', stat_type: 'hits', line: 0.5, direction: 'over', over_odds: -600, under_odds: 400,
    });
    // -600 is past the JUICE_ODDS_FLOOR (-400) → refused before we even get here
    expect(out.grade).toBeNull();
    expect(out.suppressed_reason).toBe('juiced_no_edge');
  });
});
