'use strict';

const {
  getFutures, priceMove, attachMoves, linkNewsToMoves,
} = require('../../src/services/futuresService');

const outrightEvents = [
  {
    id: 'evt-ws',
    sport_key: 'baseball_mlb_world_series_winner',
    sport_title: 'MLB World Series Winner',
    bookmakers: [
      {
        key: 'draftkings',
        markets: [{ key: 'outrights', outcomes: [
          { name: 'Los Angeles Dodgers', price: 300 },
          { name: 'New York Yankees', price: 500 },
        ] }],
      },
    ],
  },
];

describe('priceMove (pure)', () => {
  test('shortening when payout drops (+400 → +300)', () => {
    expect(priceMove(400, 300)).toBe('shortening');
  });
  test('drifting when payout rises (+300 → +450)', () => {
    expect(priceMove(300, 450)).toBe('drifting');
  });
  test('flat within epsilon / unusable prices', () => {
    expect(priceMove(300, 300)).toBe('flat');
    expect(priceMove(null, 300)).toBe('flat');
    expect(priceMove(300, null)).toBe('flat');
  });
  test('sign correct across the +/- american boundary', () => {
    // +120 (dec 2.2) → -110 (dec 1.909): payout dropped → shortening
    expect(priceMove(120, -110)).toBe('shortening');
    // -110 → +120: payout rose → drifting
    expect(priceMove(-110, 120)).toBe('drifting');
  });
});

describe('attachMoves (pure)', () => {
  const cur = [{ key: 'k', title: 'K', selections: [
    { name: 'A', price: 300 }, { name: 'B', price: 500 },
  ] }];

  test('first fetch (no prev) → move:flat, no prevPrice', () => {
    const out = attachMoves(cur, undefined);
    expect(out[0].selections[0]).toEqual({ name: 'A', price: 300, move: 'flat' });
  });

  test('diffs against prev cached markets → prevPrice + move', () => {
    const prev = [{ key: 'k', selections: [{ name: 'A', price: 400 }, { name: 'B', price: 500 }] }];
    const out = attachMoves(cur, prev);
    const a = out[0].selections.find((s) => s.name === 'A');
    expect(a.prevPrice).toBe(400);
    expect(a.move).toBe('shortening'); // 400 → 300
    const b = out[0].selections.find((s) => s.name === 'B');
    expect(b.prevPrice).toBe(500);
    expect(b.move).toBe('flat'); // unchanged
  });
});

describe('linkNewsToMoves (pure causal tie)', () => {
  const news = [
    { headline: 'Dodgers acquire ace at deadline', published: '2026-07-30T12:00:00Z' },
    { headline: 'Unrelated older story', published: '2026-06-01T12:00:00Z' },
  ];
  test('matches a move to the most-recent PRECEDING headline in window', () => {
    const moves = [{ key: 'k', name: 'Los Angeles Dodgers', at: '2026-07-31T00:00:00Z' }];
    const links = linkNewsToMoves(news, moves, 48);
    expect(links).toEqual([{ moveKey: 'k|Los Angeles Dodgers', headline: 'Dodgers acquire ace at deadline' }]);
  });
  test('no headline within window → no cause (never invents)', () => {
    const moves = [{ key: 'k', name: 'X', at: '2026-08-15T00:00:00Z' }];
    expect(linkNewsToMoves(news, moves, 48)).toEqual([]);
  });
  test('headline AFTER the move is not a cause', () => {
    const moves = [{ key: 'k', name: 'X', at: '2026-07-29T00:00:00Z' }];
    expect(linkNewsToMoves(news, moves, 48)).toEqual([]);
  });
});

describe('getFutures (injectable, offline)', () => {
  const baseDeps = { apiKey: 'test', cacheGet: async () => null, cacheSet: async () => {} };

  test('unknown sport → { markets: [] }, no fetch', async () => {
    const axios = { get: jest.fn() };
    const out = await getFutures('cricket', { ...baseDeps, axios });
    expect(out.markets).toEqual([]);
    expect(out.sport).toBe('cricket');
    expect(axios.get).not.toHaveBeenCalled();
  });

  test('quota gate OFF (enabled:false) → { markets: [] }, no fetch', async () => {
    const axios = { get: jest.fn() };
    const out = await getFutures('mlb', { ...baseDeps, axios, enabled: false });
    expect(out.markets).toEqual([]);
    expect(axios.get).not.toHaveBeenCalled();
  });

  test('cold cache + enabled → fetches once, normalizes markets', async () => {
    const axios = { get: jest.fn().mockResolvedValue({ data: outrightEvents, headers: {} }) };
    const cacheSet = jest.fn(async () => {});
    const out = await getFutures('mlb', { ...baseDeps, axios, enabled: true, ttl: 3600, cacheSet });
    expect(axios.get).toHaveBeenCalledTimes(1);
    // single outrights call
    expect(axios.get.mock.calls[0][1].params.markets).toBe('outrights');
    expect(out.markets).toHaveLength(1);
    expect(out.markets[0].key).toBe('baseball_mlb_world_series_winner');
    const dodgers = out.markets[0].selections.find((s) => s.name === 'Los Angeles Dodgers');
    expect(dodgers.price).toBe(300);
    expect(dodgers.move).toBe('flat'); // no prev
    expect(cacheSet).toHaveBeenCalled();
  });

  test('price move shortening from a prev-price cache', async () => {
    const prevCache = {
      updated_at: '2000-01-01T00:00:00Z', // ancient → stale → refetch
      markets: [{ key: 'baseball_mlb_world_series_winner', selections: [
        { name: 'Los Angeles Dodgers', price: 450 }, // was +450, now +300 → shortening
      ] }],
    };
    const axios = { get: jest.fn().mockResolvedValue({ data: outrightEvents, headers: {} }) };
    const out = await getFutures('mlb', {
      apiKey: 'test', axios, enabled: true, ttl: 3600,
      cacheGet: async () => prevCache, cacheSet: async () => {},
    });
    const dodgers = out.markets[0].selections.find((s) => s.name === 'Los Angeles Dodgers');
    expect(dodgers.prevPrice).toBe(450);
    expect(dodgers.move).toBe('shortening');
  });

  test('fresh cache → served without fetching', async () => {
    const fresh = {
      updated_at: new Date().toISOString(),
      markets: [{ key: 'baseball_mlb_world_series_winner', title: 'WS', selections: [{ name: 'A', price: 100 }] }],
    };
    const axios = { get: jest.fn() };
    const out = await getFutures('mlb', {
      apiKey: 'test', axios, enabled: true, ttl: 3600,
      cacheGet: async () => fresh, cacheSet: async () => {},
    });
    expect(axios.get).not.toHaveBeenCalled();
    expect(out.markets).toEqual(fresh.markets);
  });

  test('fetch failure → empty (never throws, never fabricates)', async () => {
    const axios = { get: jest.fn().mockRejectedValue(new Error('boom')) };
    const out = await getFutures('mlb', { ...baseDeps, axios, enabled: true, ttl: 3600 });
    expect(out.markets).toEqual([]);
  });

  test('empty board with no prior cache → { markets: [] }', async () => {
    const axios = { get: jest.fn().mockResolvedValue({ data: [], headers: {} }) };
    const out = await getFutures('mlb', { ...baseDeps, axios, enabled: true, ttl: 3600 });
    expect(out.markets).toEqual([]);
  });
});
