// Session 62 (A1-S1) — the feature-promise locks. Every pricing claim that
// was vapor is now real, and these tests keep it that way.

const { quarterKelly, americanToDecimal } = require('../../src/utils/kelly');
const { applyTierGating } = require('../../src/utils/tierGating');
const { getScanLimit } = require('../../src/config/tiers');
const fs = require('fs');
const path = require('path');
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');

describe('quarter-Kelly (pricing: "Quarter-Kelly sizing recommendations")', () => {
  test('computes from real probability × real odds', () => {
    // p=0.55 at -110: b=0.909, f=(0.909*0.55-0.45)/0.909 ≈ 0.055 → quarter ≈ 1.4%
    const k = quarterKelly(0.55, -110);
    expect(k.quarter).toBeCloseTo(0.0138, 3);
    expect(k.pct).toBeCloseTo(1.4, 1);
  });
  test('no edge → null (Kelly says: no bet, never a fabricated size)', () => {
    expect(quarterKelly(0.5, -110)).toBeNull();
  });
  test('missing inputs → null, never defaults', () => {
    expect(quarterKelly(null, -110)).toBeNull();
    expect(quarterKelly(0.6, null)).toBeNull();
    expect(quarterKelly(1.2, -110)).toBeNull();
  });
  test('american conversion both signs', () => {
    expect(americanToDecimal(-110)).toBeCloseTo(1.909, 3);
    expect(americanToDecimal(150)).toBeCloseTo(2.5, 3);
  });
});

describe('Desk-only gate at the API (alt ladder + kelly never leave the server below Desk)', () => {
  const RESULT = { grade: 'A', confidence: 70, alt_lines: [{ line: 1.5, grade: 'A' }], kelly: { pct: 1.4 }, reasoning: { summary: 'x' }, kill_conditions_triggered: [] };
  test('desk keeps both', () => {
    const g = applyTierGating(RESULT, 'desk');
    expect(g.alt_lines).toBeTruthy();
    expect(g.kelly).toBeTruthy();
  });
  test('analyst loses both, keeps reasoning', () => {
    const g = applyTierGating(RESULT, 'analyst');
    expect(g.alt_lines).toBeUndefined();
    expect(g.kelly).toBeUndefined();
    expect(g.reasoning.summary).toBe('x');
  });
  test('free loses both AND gets locked reasoning', () => {
    const g = applyTierGating(RESULT, 'free');
    expect(g.alt_lines).toBeUndefined();
    expect(g.kelly).toBeUndefined();
    expect(g.reasoning.locked).toBe(true);
  });
});

describe('read limits match the pricing page', () => {
  test('free 3/day · africa 10/day · analyst unlimited (Founder promise) · desk unlimited', () => {
    expect(getScanLimit('free')).toBe(3);
    expect(getScanLimit('africa')).toBe(10);
    expect(getScanLimit('analyst')).toBe(Infinity);
    expect(getScanLimit('desk')).toBe(Infinity);
  });
});

describe('no unverifiable factor-count claims anywhere', () => {
  it.each([
    'components/Pricing.tsx', 'app/help/page.tsx', 'app/about/page.tsx',
    'app/upgrade/desk/page.tsx', 'components/GradeCard.tsx',
    'components/vyndr/GradeResultCard.tsx', 'components/vyndr/ProcessingGrade.tsx',
  ])('%s carries no "40+" claim', (rel) => {
    expect(read(rel)).not.toContain('40+');
  });
});

describe('free tier sees the promised LOCKED PREVIEW of kill conditions', () => {
  const { mapScanToGradeResult } = require('../../web/src/lib/gradeAdapter');
  test('free gets code-only teaser chips; analyst gets reasons', () => {
    const input = {
      player: 'X', stat: 'hits', line: 1.5, direction: 'over', grade: 'B',
      kill_conditions: [{ code: 'TRAP', reason: 'Multiple trap signals firing.' }],
    };
    const free = mapScanToGradeResult({ ...input, tier: 'free' });
    expect(free.killConditions).toEqual(['TRAP — upgrade to see details']);
    const paid = mapScanToGradeResult({ ...input, tier: 'analyst' });
    expect(paid.killConditions).toEqual(['Multiple trap signals firing.']);
  });
});
