import express from 'express';
import request from 'supertest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const state = vi.hoisted(() => ({
  curated: null as any,
  cachedForecast: null as any,
  cachedForecastByTeams: null as any,
  newsRows: [] as any[],
  hangNewsQuery: false,
  hangLineMovementQuery: false,
  playerInjuries: [] as any[],
  sportsclawInjuries: [] as any[],
  activePlayers: [] as any[],
  exactLineMovementRows: [] as any[],
  fallbackLineMovementRows: [] as any[],
  exactGameOddsRows: [] as any[],
  fallbackGameOddsRows: [] as any[],
  fallbackLineMovementParams: null as any,
  fallbackGameOddsParams: null as any,
  topPropRows: [] as any[],
  topGameRows: [] as any[],
  sourceSummaryRows: [] as any[],
  teamPropsCacheRows: [] as any[],
  teamPropsPrecomputedRows: [] as any[],
  teamPropCandidatesHome: [] as any[],
  teamPropCandidatesAway: [] as any[],
  mlbPropCandidatesHome: [] as any[],
  mlbPropCandidatesAway: [] as any[],
  directMlbPropCandidatesHome: [] as any[],
  directMlbPropCandidatesAway: [] as any[],
  topPickEntries: [] as any[],
  throwGameDataQuery: false,
}));

const mocked = vi.hoisted(() => ({
  optionalAuth: vi.fn((_req: any, _res: any, next: any) => next()),
  authMiddleware: vi.fn((_req: any, _res: any, next: any) => next()),
  getCachedForecast: vi.fn(),
  getCachedForecastByTeams: vi.fn(),
  poolQuery: vi.fn(),
  fetchTeamPropMarketCandidates: vi.fn(),
  fetchMlbPropCandidates: vi.fn(),
  fetchDirectMlbPropCandidates: vi.fn(),
  buildTopPropsOfDay: vi.fn(() => []),
  buildTopPickEntries: vi.fn(() => state.topPickEntries),
  assessPublicGameFit: vi.fn(() => ({ eligible: true })),
}));

vi.mock('../../middleware/auth', () => ({
  authMiddleware: mocked.authMiddleware,
  optionalAuth: mocked.optionalAuth,
}));
vi.mock('../../models/user', () => ({
  findUserById: vi.fn(),
  deductPick: vi.fn(),
  getPickBalance: vi.fn(async () => ({ single_picks: 0, daily_pass_picks: 0, daily_pass_valid: false, daily_free_forecasts: 0 })),
  ensureDailyGrant: vi.fn(),
  isInGracePeriod: vi.fn(() => false),
}));
vi.mock('../../models/pick', () => ({ hasUserPurchasedPick: vi.fn(), recordPick: vi.fn(), deletePick: vi.fn() }));
vi.mock('../../models/forecast', () => ({
  getCachedForecast: mocked.getCachedForecast,
  getCachedForecastByTeams: mocked.getCachedForecastByTeams,
  cacheForecast: vi.fn(),
  updateCachedOdds: vi.fn(),
}));
vi.mock('../../models/ledger', () => ({ recordLedgerEntry: vi.fn(), LedgerReason: {} }));
vi.mock('../../services/sgo', () => ({
  fetchEvents: vi.fn(async () => []),
  parseOdds: vi.fn(),
  sanitizeGameOddsForLeague: vi.fn((_league: string | null | undefined, odds: any) => odds),
  sanitizeMoneylinePair: vi.fn((pair: any) => pair || { home: null, away: null }),
  LEAGUE_MAP: { nba: 'nba', mlb: 'mlb', nhl: 'nhl', epl: 'soccer_epl', bundesliga: 'soccer_germany_bundesliga' },
}));
vi.mock('../../services/grok', () => ({
  buildMlbFallbackProps: vi.fn(),
  buildSourceBackedFallbackProps: vi.fn(),
  generateForecast: vi.fn(),
  generateTeamProps: vi.fn(),
  generateSteamInsight: vi.fn(),
  generateSharpInsight: vi.fn(),
}));
vi.mock('../../services/forecast-builder', () => ({ buildForecastFromCuratedEvent: vi.fn() }));
vi.mock('../../services/dvp', () => ({ generateDvpInsight: vi.fn(), hasDvpData: vi.fn(async () => false) }));
vi.mock('../../services/home-field-scout', () => ({ generateHcwInsight: vi.fn(), hasHcwData: vi.fn(() => false) }));
vi.mock('../../services/prop-dedup', () => ({ deduplicateProps: vi.fn((props: any[]) => props) }));
vi.mock('../../services/rie', () => ({ getLegacyCompositeView: vi.fn(), getStoredPayloadExtensions: vi.fn() }));
vi.mock('../../services/forecast-asset-taxonomy', () => ({
  describeForecastAsset: vi.fn(() => ({ playerRole: null })),
  getMlbPlayerRole: vi.fn(() => null),
}));
vi.mock('../../services/top-props', () => ({ buildTopPropsOfDay: mocked.buildTopPropsOfDay }));
vi.mock('../../services/top-picks', () => ({ buildTopPickEntries: mocked.buildTopPickEntries }));
vi.mock('../../services/top-game-profile', () => ({ assessPublicGameFit: mocked.assessPublicGameFit }));
vi.mock('../../services/canonical-names', () => ({
  isPlayerOnTeam: vi.fn(() => true),
  resolveCanonicalName: vi.fn((value: string) => value),
}));
vi.mock('../../services/player-prop-market-registry', () => ({
  getPlayerPropLabelForLeague: vi.fn((_league: string, value: string) => value),
  getSupportedPlayerPropQueryValues: vi.fn(() => ['points', 'rebounds', 'assists', 'shots']),
  getTheOddsPlayerPropMarketKeyForLeague: vi.fn(),
  resolvePlayerPropStatIdentity: vi.fn((params: any) => ({
    statType: params.normalizedStatType || params.statType || null,
    normalizedStatType: params.normalizedStatType || params.statType || null,
  })),
}));
vi.mock('../../services/player-prop-signals', () => ({
  buildPlayerPropMetadata: vi.fn(),
  buildPlayerPropSignal: vi.fn(),
}));
vi.mock('../../services/player-prop-storage', () => ({
  buildStoredPlayerPropPayload: vi.fn(),
  shouldPersistStandalonePlayerPropMetadata: vi.fn(() => true),
  shouldRenderTeamPropBundleEntry: vi.fn(() => true),
}));
vi.mock('../../services/player-prop-radar', () => ({
  buildFeaturedPlayersFromRows: vi.fn(() => []),
}));
vi.mock('../../services/team-prop-market-candidates', () => ({
  fetchTeamPropMarketCandidates: mocked.fetchTeamPropMarketCandidates,
}));
vi.mock('../../services/mlb-prop-markets', () => ({
  fetchMlbPropCandidates: mocked.fetchMlbPropCandidates,
  fetchDirectMlbPropCandidates: mocked.fetchDirectMlbPropCandidates,
}));
vi.mock('../../services/piff', () => ({ getPiffPropsForGame: vi.fn(() => []), loadPiffPropsForDate: vi.fn(() => ({})) }));
vi.mock('../../services/digimon', () => ({ getDigimonForGame: vi.fn(() => []) }));
vi.mock('../../services/clv-picks', () => ({ recordClvPick: vi.fn() }));
vi.mock('../../services/insight-source', () => ({ fetchInsightSourceData: vi.fn() }));
vi.mock('../../services/public-forecast-normalizer', () => ({
  normalizePublicForecastNarrative: vi.fn(),
  reconcilePublicForecastProjection: vi.fn(),
}));
vi.mock('../../services/tiktok', () => ({ trackTikTokWebEvent: vi.fn() }));
vi.mock('../../db', () => ({ default: { query: mocked.poolQuery } }));

function installQueryMock() {
  mocked.poolQuery.mockImplementation(async (sql: string, values?: any[]) => {
    const text = String(sql);

    if (text.includes('SELECT * FROM rm_events WHERE event_id = $1')) {
      return { rows: state.curated ? [state.curated] : [] };
    }
    if (text.includes('FROM "PlayerInjury"') && text.includes("LOWER(status) = ANY")) {
      return { rows: state.playerInjuries };
    }
    if (text.includes('FROM sportsclaw.injuries')) {
      return { rows: state.sportsclawInjuries };
    }
    if (text.includes('FROM "PlayerInjury"') && text.includes("LOWER(status) = 'active'")) {
      return { rows: state.activePlayers };
    }
    if (text.includes('FROM rm_news_links') && text.includes('FROM rm_blog_posts')) {
      if (state.hangNewsQuery) {
        return new Promise(() => {});
      }
      return { rows: state.newsRows };
    }
    if (text.includes('FROM "LineMovement"') && text.includes('= ANY($3::text[])')) {
      if (state.hangLineMovementQuery) {
        return new Promise(() => {});
      }
      return { rows: state.exactLineMovementRows };
    }
    if (text.includes('FROM "LineMovement"')) {
      if (state.hangLineMovementQuery) {
        return new Promise(() => {});
      }
      state.fallbackLineMovementParams = values ?? null;
      return { rows: state.fallbackLineMovementRows };
    }
    if (text.includes('FROM "GameOdds"') && text.includes('= ANY($3::text[])')) {
      return { rows: state.exactGameOddsRows };
    }
    if (text.includes('FROM "GameOdds"')) {
      state.fallbackGameOddsParams = values ?? null;
      return { rows: state.fallbackGameOddsRows };
    }
    if (text.includes("FROM rm_forecast_precomputed fp") && text.includes("fp.forecast_type = 'PLAYER_PROP'")) {
      return { rows: state.topPropRows };
    }
    if (text.includes('FROM "PlayerPropLine"')) {
      if (text.includes('COUNT(DISTINCT')) {
        const players = new Set<string>();
        const marketKeys = new Set<string>();
        for (const row of state.sourceSummaryRows) {
          const player = String(row.playerExternalId || row.marketName || '')
            .replace(/_\d+_[A-Z]+$/, '')
            .replace(/_/g, ' ')
            .replace(/\s+Over\/Under$/i, '')
            .trim()
            .toLowerCase();
          if (!player) continue;
          players.add(player);
          const sideRaw = String(row.raw?.side || row.market || '').trim().toLowerCase();
          const side = sideRaw === 'o' || sideRaw.startsWith('over') || sideRaw.includes('_over')
            ? 'over'
            : sideRaw === 'u' || sideRaw.startsWith('under') || sideRaw.includes('_under')
              ? 'under'
              : '';
          if (!side) continue;
          marketKeys.add(`${player}|${String(row.propType || '').toLowerCase()}|${Number(row.lineValue)}|${side}`);
        }
        return {
          rows: [{
            player_count: players.size,
            market_count: marketKeys.size,
          }],
        };
      }
      return { rows: state.sourceSummaryRows };
    }
    if (text.includes('SELECT team, props_data FROM rm_team_props_cache WHERE event_id = $1')) {
      return { rows: state.teamPropsCacheRows };
    }
    if (text.includes('FROM rm_forecast_precomputed') && text.includes("forecast_type = 'TEAM_PROPS'")) {
      return { rows: state.teamPropsPrecomputedRows };
    }
    if (text.includes('FROM rm_events e') && text.includes('fc_confidence')) {
      return { rows: state.topGameRows };
    }
    if (text.includes('FROM rm_forecast_precomputed') && text.includes("forecast_type = 'PLAYER_PROP'")) {
      return { rows: [] };
    }
    if (text.includes('FROM rm_insight_unlocks')) {
      return { rows: [] };
    }
    return { rows: [] };
  });
}

async function loadBundleHelper() {
  vi.resetModules();
  return (await import('../forecast')).buildDigilanderBundleBody;
}

describe('Digilander bundled contract', () => {
  beforeEach(() => {
    vi.clearAllMocks();

    state.curated = {
      event_id: 'evt1',
      league: 'nba',
      home_team: 'Los Angeles Lakers',
      away_team: 'Boston Celtics',
      home_short: 'LAL',
      away_short: 'BOS',
      starts_at: '2026-04-15T23:30:00.000Z',
      moneyline: { home: -120, away: 110 },
      spread: { home: { line: -3.5, odds: -110 }, away: { line: 3.5, odds: -110 } },
      total: { over: { line: 224.5, odds: -110 }, under: { line: 224.5, odds: -110 } },
    };
    state.cachedForecast = {
      forecast_data: {},
      confidence_score: 0.64,
      composite_confidence: 0.67,
      home_team: 'Los Angeles Lakers',
      away_team: 'Boston Celtics',
      league: 'nba',
      odds_data: null,
      model_signals: { modelSignals: null },
      created_at: '2026-04-15T18:00:00.000Z',
    };
    state.cachedForecastByTeams = null;
    state.newsRows = [];
    state.hangNewsQuery = false;
    state.hangLineMovementQuery = false;
    state.playerInjuries = [];
    state.sportsclawInjuries = [];
    state.activePlayers = [];
    state.exactLineMovementRows = [];
    state.fallbackLineMovementRows = [];
    state.exactGameOddsRows = [];
    state.fallbackGameOddsRows = [];
    state.fallbackLineMovementParams = null;
    state.fallbackGameOddsParams = null;
    state.topPropRows = [];
    state.topGameRows = [];
    state.sourceSummaryRows = [];
    state.teamPropsCacheRows = [];
    state.teamPropsPrecomputedRows = [];
    state.teamPropCandidatesHome = [];
    state.teamPropCandidatesAway = [];
    state.mlbPropCandidatesHome = [];
    state.mlbPropCandidatesAway = [];
    state.directMlbPropCandidatesHome = [];
    state.directMlbPropCandidatesAway = [];
    state.topPickEntries = [];
    state.throwGameDataQuery = false;

    mocked.getCachedForecast.mockImplementation(async () => state.cachedForecast);
    mocked.getCachedForecastByTeams.mockImplementation(async () => state.cachedForecastByTeams);
    mocked.fetchTeamPropMarketCandidates.mockImplementation(async (params: any) => {
      if (state.throwGameDataQuery) {
        return new Promise(() => {});
      }
      return String(params.teamShort || '').toUpperCase() === 'LAL' ? state.teamPropCandidatesHome : state.teamPropCandidatesAway;
    });
    mocked.fetchMlbPropCandidates.mockImplementation(async (params: any) => (
      String(params.teamShort || '').toUpperCase() === 'LAL' ? state.mlbPropCandidatesHome : state.mlbPropCandidatesAway
    ));
    mocked.fetchDirectMlbPropCandidates.mockImplementation(async (params: any) => (
      String(params.teamShort || '').toUpperCase() === 'LAL' ? state.directMlbPropCandidatesHome : state.directMlbPropCandidatesAway
    ));

    installQueryMock();
  });

  it('returns summary-mode modules with null data and honest coverage counts', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-04-15T18:10:00.000Z'));

    try {
    state.newsRows = [
      {
        id: 'news-1',
        title: 'Lakers vs Celtics injury watch',
        description: 'Los Angeles Lakers and Boston Celtics injury report update.',
        source: 'AP',
        source_display: 'AP',
        sport: 'nba',
      },
    ];
    state.playerInjuries = [
      {
        playerName: 'LeBron James',
        team: 'Los Angeles Lakers',
        position: 'F',
        status: 'Questionable',
        injuryType: 'Ankle',
        description: 'Late status check.',
        source: 'Team',
        reportedAt: '2026-04-15T18:00:00.000Z',
      },
    ];
    state.exactGameOddsRows = [
      {
        bookmaker: 'DraftKings',
        market: 'moneyline',
        lineValue: null,
        homeOdds: -120,
        awayOdds: 110,
        overOdds: null,
        underOdds: null,
        fetchedAt: '2026-04-15T18:05:00.000Z',
        isBestLine: true,
        source: 'The Odds API',
      },
    ];
    state.teamPropCandidatesHome = [
      {
        player: 'LeBron James',
        marketLineValue: 27.5,
        overOdds: -105,
        underOdds: -115,
        availableSides: ['over', 'under'],
      },
    ];
    state.topPickEntries = [
      {
        kind: 'game',
        game: { eventId: 'evt1' },
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({ eventId: 'evt1', summaryOnly: true, signedIn: false });

    expect(body.contractVersion).toBe('digilander-v1');
    expect(body.preview.data).toBeNull();
    expect(body.news.data).toBeNull();
    expect(body.gameData.data).toBeNull();
    expect(body.market.data).toBeNull();
    expect(body.playerProps.data).toBeNull();
    expect(body.teamBreakdown.data).toBeNull();

    expect(body.coverage).toMatchObject({
      preview: { available: true, status: 'ready', count: 1, readiness: 'full', coverageConfidence: 'high' },
      bestPlays: { available: true, status: 'ready', count: 3, readiness: 'full', coverageConfidence: 'high' },
      playerProps: { available: true, status: 'ready', count: 2, readiness: 'partial', coverageConfidence: 'medium', publishedCount: 0, marketCount: 2, featuredPlayerCount: 0 },
      playerData: { available: true, status: 'ready', count: 3, readiness: 'full', coverageConfidence: 'high', playerCount: 1, lineBackedCount: 2 },
      injuries: { available: true, status: 'ready', count: 1, readiness: 'full', coverageConfidence: 'high' },
      news: { available: true, status: 'ready', count: 1, readiness: 'partial', coverageConfidence: 'medium' },
      market: {
        available: true,
        status: 'ready',
        count: 1,
        readiness: 'partial',
        coverageConfidence: 'medium',
        lineMovementCount: 0,
        bookSnapshotCount: 1,
        bookmakerCount: 1,
        freshness: 'fresh',
        lastSnapshotAt: '2026-04-15T18:05:00.000Z',
      },
      teamBreakdown: { available: true, status: 'ready', count: 2, readiness: 'partial', coverageConfidence: 'medium' },
    });
    expect(body.diagnostics).toMatchObject({
      scope: 'public',
      summaryOnly: true,
      cacheable: true,
      request: {
        cacheStatus: 'miss',
        coalesced: false,
      },
      fallbackUsage: {
        news: {
          source: 'live',
          fallbackUsed: false,
          itemCount: 1,
        },
        market: {
          lineMovementSource: 'none',
          bookSnapshotSource: 'exact',
          fallbackUsed: false,
        },
        summarySupport: {
          source: 'team_prop_candidates',
          fallbackUsed: true,
          playerCount: 1,
          linePropsCount: 0,
          marketPropsCount: 2,
        },
      },
      moduleReadiness: {
        market: {
          readiness: 'partial',
          coverageConfidence: 'medium',
          signals: {
            lineMovementCount: 0,
            bookSnapshotCount: 1,
            freshness: 'fresh',
          },
        },
        playerProps: {
          readiness: 'partial',
          coverageConfidence: 'medium',
          signals: {
            publishedCount: 0,
            sourceCount: 2,
            normalizedCount: 0,
            fallbackCount: 2,
            exposedCount: 2,
            marketCount: 2,
          },
        },
      },
    });
    expect(body.diagnostics.timingsMs.total).toBeGreaterThanOrEqual(0);
    expect(body.diagnostics.timingsMs.summarySupport).toBeGreaterThanOrEqual(0);
    } finally {
      vi.useRealTimers();
    }
  });

  it('marks failed player data as unavailable even when team breakdown still has rows', async () => {
    vi.useFakeTimers();

    try {
      state.curated = {
        ...state.curated,
        event_id: 'evt-player-data-failed',
      };
      state.throwGameDataQuery = true;
      state.playerInjuries = [
        {
          playerName: 'LeBron James',
          team: 'Los Angeles Lakers',
          position: 'F',
          status: 'Questionable',
          injuryType: 'Ankle',
          description: 'Late status check.',
          source: 'Team',
          reportedAt: '2026-04-15T18:00:00.000Z',
        },
      ];
      state.teamPropsCacheRows = [
        {
          team: 'home',
          props_data: {
            playerProps: [
              { player: 'LeBron James', prop: 'Points 27.5', recommendation: 'Over' },
            ],
          },
        },
      ];

      const buildDigilanderBundleBody = await loadBundleHelper();
      const pending = buildDigilanderBundleBody({ eventId: 'evt-player-data-failed', summaryOnly: false, signedIn: false });
      await vi.advanceTimersByTimeAsync(1000);
      const body = await pending;

      expect(body.coverage.playerData.status).toBe('failed');
      expect(body.coverage.playerData.available).toBe(false);
      expect(body.coverage.playerData.reason).toBe('Game data timed out');
      expect(body.coverage.playerData.lineBackedCount).toBeGreaterThan(0);
      expect(body.coverage.injuries.status).toBe('ready');
      expect(body.coverage.injuries.available).toBe(true);
      expect(body.coverage.injuries.count).toBe(1);
      expect(body.injuries).toMatchObject({
        status: 'ready',
        reason: null,
        count: 1,
        data: [
          expect.objectContaining({
            playerName: 'LeBron James',
            status: 'Questionable',
          }),
        ],
      });
      expect(body.coverage.teamBreakdown.status).toBe('ready');
      expect(body.coverage.teamBreakdown.available).toBe(true);
    } finally {
      vi.useRealTimers();
    }
  });

  it('serves market-history without auth as the public-safe market contract', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-04-15T18:10:00.000Z'));

    try {
      state.exactLineMovementRows = [
        {
          marketType: 'moneyline',
          openLine: -125,
          currentLine: -120,
          closingLine: null,
          lineMovement: 5,
          movementDirection: 'toward_home',
          openOdds: -125,
          currentOdds: -120,
          closingOdds: null,
          steamMove: false,
          reverseLineMove: false,
          recordedAt: '2026-04-15T18:05:00.000Z',
        },
      ];
      state.exactGameOddsRows = [
        {
          bookmaker: 'DraftKings',
          market: 'moneyline',
          lineValue: null,
          homeOdds: -120,
          awayOdds: 110,
          overOdds: null,
          underOdds: null,
          fetchedAt: '2026-04-15T18:05:00.000Z',
          isBestLine: true,
          source: 'The Odds API',
        },
      ];

      const { default: router } = await import('../forecast');
      const app = express();
      app.use('/', router);

      const res = await request(app).get('/evt1/market-history');

      expect(res.status).toBe(200);
      expect(res.body).toMatchObject({
        eventId: 'evt1',
        league: 'nba',
        homeTeam: 'Los Angeles Lakers',
        awayTeam: 'Boston Celtics',
        lineMovement: [
          {
            marketType: 'moneyline',
            openLine: -125,
            currentLine: -120,
            lineMovement: 5,
          },
        ],
        bookSnapshots: [
          {
            bookmaker: 'DraftKings',
            market: 'moneyline',
            homeOdds: -120,
            awayOdds: 110,
          },
        ],
        summary: {
          bookmakerCount: 1,
          bookSnapshotCount: 1,
          lineMovementCount: 1,
          freshness: 'fresh',
          bestBookNow: {
            bookmaker: 'DraftKings',
            marketType: 'moneyline',
            label: 'Moneyline',
          },
          lineChangedFrom: {
            marketType: 'moneyline',
            label: 'Moneyline',
            openLine: -125,
            currentLine: -120,
            delta: 5,
          },
        },
      });
      expect(mocked.authMiddleware).not.toHaveBeenCalled();
    } finally {
      vi.useRealTimers();
    }
  });

  it('returns full public digilander market data without requiring sign-in', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-04-15T18:10:00.000Z'));

    try {
      state.exactGameOddsRows = [
        {
          bookmaker: 'DraftKings',
          market: 'moneyline',
          lineValue: null,
          homeOdds: -120,
          awayOdds: 110,
          overOdds: null,
          underOdds: null,
          fetchedAt: '2026-04-15T18:05:00.000Z',
          isBestLine: true,
          source: 'The Odds API',
        },
      ];

      const { default: router } = await import('../forecast');
      const app = express();
      app.use('/', router);

      const res = await request(app).get('/evt1/digilander');

      expect(res.status).toBe(200);
      expect(res.body.signedIn).toBe(false);
      expect(res.body.coverage.market).toMatchObject({
        available: true,
        status: 'ready',
        count: 1,
        lineMovementCount: 0,
        bookSnapshotCount: 1,
        bookmakerCount: 1,
        freshness: 'fresh',
        lastSnapshotAt: '2026-04-15T18:05:00.000Z',
      });
      expect(res.body.market).toMatchObject({
        status: 'ready',
        reason: null,
        count: 1,
        data: {
          summary: {
            bookmakerCount: 1,
            bookSnapshotCount: 1,
            lineMovementCount: 0,
            freshness: 'fresh',
            bestBookNow: {
              bookmaker: 'DraftKings',
              marketType: 'moneyline',
              label: 'Moneyline',
            },
          },
        },
      });
      expect(mocked.optionalAuth).toHaveBeenCalled();
      expect(mocked.authMiddleware).not.toHaveBeenCalled();
    } finally {
      vi.useRealTimers();
    }
  });

  it('returns partial summary coverage with mixed ready and empty states', async () => {
    state.topPickEntries = [
      {
        kind: 'game',
        game: { eventId: 'evt1' },
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({ eventId: 'evt1', summaryOnly: true, signedIn: false });

    expect(body.coverage).toMatchObject({
      preview: { available: true, status: 'ready', count: 1, readiness: 'full', coverageConfidence: 'high' },
      bestPlays: { available: true, status: 'ready', count: 1, readiness: 'full', coverageConfidence: 'high' },
      playerProps: { available: false, status: 'empty', count: 0, readiness: 'empty', coverageConfidence: 'low' },
      playerData: { available: false, status: 'empty', count: 0, readiness: 'empty', coverageConfidence: 'low', playerCount: 0, lineBackedCount: 0 },
      injuries: { available: false, status: 'empty', count: 0, readiness: 'empty', coverageConfidence: 'low' },
      news: { available: false, status: 'empty', count: 0, readiness: 'empty', coverageConfidence: 'low' },
      market: { available: false, status: 'empty', count: 0, readiness: 'empty', coverageConfidence: 'low', lineMovementCount: 0, bookSnapshotCount: 0 },
      teamBreakdown: { available: false, status: 'empty', count: 0, readiness: 'empty', coverageConfidence: 'low' },
    });
    expect(body.coverage.newsCoverageReason).toBe('No matchup-specific news hits');
    expect(body.coverage.playerProps.reason).toBe('No player props for this game');
    expect(body.coverage.playerData.reason).toBe('No game data for this event');
    expect(body.coverage.injuries.reason).toBe('No injuries for this game');
    expect(body.coverage.market.reason).toBe('No market history for this game');
    expect(body.coverage.market.freshness).toBe('none');
    expect(body.diagnostics.moduleReadiness.market).toMatchObject({
      readiness: 'empty',
      coverageConfidence: 'low',
      signals: {
        lineMovementCount: 0,
        bookSnapshotCount: 0,
        freshness: 'none',
      },
    });
    expect(body.playerProps).toEqual({
      status: 'empty',
      reason: 'No player props for this game',
      count: 0,
      data: null,
    });
    expect(body.market).toEqual({
      status: 'empty',
      reason: 'No market history for this game',
      count: 0,
      data: null,
    });
  });

  it('coalesces duplicate public bundle builds while the first request is still running', async () => {
    let releaseForecast: (() => void) | null = null;
    mocked.getCachedForecast.mockImplementation(() => new Promise((resolve) => {
      releaseForecast = () => resolve(state.cachedForecast);
    }));

    const buildDigilanderBundleBody = await loadBundleHelper();
    const firstRequest = buildDigilanderBundleBody({ eventId: 'evt1', summaryOnly: true, signedIn: false });

    await new Promise((resolve) => setTimeout(resolve, 0));

    const secondRequest = buildDigilanderBundleBody({ eventId: 'evt1', summaryOnly: true, signedIn: false });
    expect(mocked.getCachedForecast).toHaveBeenCalledTimes(1);

    if (releaseForecast === null) {
      throw new Error('Expected delayed forecast resolver');
    }
    (releaseForecast as unknown as () => void)();

    const [firstBody, secondBody] = await Promise.all([firstRequest, secondRequest]);
    expect(firstBody.diagnostics.request).toEqual({
      cacheStatus: 'miss',
      coalesced: false,
    });
    expect(secondBody.diagnostics.request).toEqual({
      cacheStatus: 'miss',
      coalesced: true,
    });
  });

  it('recovers soccer news and market coverage through alias matching', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-wob-fcu-20260418',
      league: 'bundesliga',
      home_team: '1. FC Union Berlin',
      away_team: 'VfL Wolfsburg',
      home_short: 'FCU',
      away_short: 'WOB',
      starts_at: '2026-04-18T13:30:00.000Z',
      moneyline: { home: 140, away: 180, draw: 225 },
      spread: { home: { line: 0.5, odds: -105 }, away: { line: -0.5, odds: -115 } },
      total: { over: { line: 2.5, odds: -110 }, under: { line: 2.5, odds: -110 } },
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: '1. FC Union Berlin',
      away_team: 'VfL Wolfsburg',
    };
    state.newsRows = [
      {
        id: 'news-1',
        title: 'Union Berlin get injury boost before Wolfsburg clash',
        description: 'Union Berlin host Wolfsburg in a key Bundesliga fixture.',
        source: 'AP',
        source_display: 'AP',
        sport: 'soccer',
      },
    ];
    state.fallbackLineMovementRows = [
      {
        homeTeam: 'Union Berlin',
        awayTeam: 'Wolfsburg',
        marketType: 'moneyline',
        openLine: 155,
        currentLine: 140,
        closingLine: null,
        lineMovement: -15,
        movementDirection: 'toward_home',
        openOdds: 155,
        currentOdds: 140,
        closingOdds: null,
        steamMove: false,
        reverseLineMove: false,
        recordedAt: '2026-04-18T11:00:00.000Z',
      },
    ];
    state.fallbackGameOddsRows = [
      {
        homeTeam: 'Union Berlin',
        awayTeam: 'Wolfsburg',
        bookmaker: 'DraftKings',
        market: 'moneyline',
        lineValue: null,
        homeOdds: 140,
        awayOdds: 180,
        overOdds: null,
        underOdds: null,
        fetchedAt: '2026-04-18T12:00:00.000Z',
        isBestLine: true,
        source: 'The Odds API',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-wob-fcu-20260418',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(body.coverage.newsCoverageReason).toBeNull();
    expect(body.coverage.market).toMatchObject({
      available: true,
      status: 'ready',
      count: 2,
      lineMovementCount: 1,
      bookSnapshotCount: 1,
    });
    expect(body.diagnostics.fallbackUsage.market).toMatchObject({
      lineMovementSource: 'fallback',
      bookSnapshotSource: 'fallback',
      fallbackUsed: true,
    });
    expect(state.fallbackLineMovementParams?.[2]).toEqual(expect.arrayContaining(['%unionberlin%']));
    expect(state.fallbackLineMovementParams?.[3]).toEqual(expect.arrayContaining(['%wolfsburg%']));
    expect(state.fallbackGameOddsParams?.[2]).toEqual(expect.arrayContaining(['%unionberlin%']));
    expect(state.fallbackGameOddsParams?.[3]).toEqual(expect.arrayContaining(['%wolfsburg%']));
  });

  it('matches blog news by structured event fields even when title text is generic', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-hsv-svw-20260418',
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
      home_short: 'SVW',
      away_short: 'HSV',
      starts_at: '2026-04-18T13:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
    };
    state.newsRows = [
      {
        id: 'blog-1',
        type: 'blog',
        title: 'Saturday preview',
        description: 'Quick look at the board.',
        source: 'rainmaker',
        source_display: 'Rainmaker',
        sport: 'soccer',
        home_team: 'Werder Bremen',
        away_team: 'Hamburger SV',
        game_date: '2026-04-18T13:30:00.000Z',
        published_at: '2026-04-18T09:00:00.000Z',
      },
      {
        id: 'blog-2',
        type: 'blog',
        title: 'Another Saturday preview',
        description: 'Wrong fixture.',
        source: 'rainmaker',
        source_display: 'Rainmaker',
        sport: 'soccer',
        home_team: 'Union Berlin',
        away_team: 'Wolfsburg',
        game_date: '2026-04-18T13:30:00.000Z',
        published_at: '2026-04-18T09:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-hsv-svw-20260418',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
  });

  it('matches accented soccer market rows through normalized exact keys', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-b04-koe-20260425',
      league: 'bundesliga',
      home_team: '1. FC Köln',
      away_team: 'Bayer 04 Leverkusen',
      home_short: 'KOE',
      away_short: 'B04',
      starts_at: '2026-04-25T13:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: '1. FC Köln',
      away_team: 'Bayer 04 Leverkusen',
    };
    state.exactGameOddsRows = [
      {
        homeTeam: '1. FC Köln',
        awayTeam: 'Bayer Leverkusen',
        bookmaker: 'DraftKings',
        market: 'moneyline',
        lineValue: null,
        homeOdds: 255,
        awayOdds: -120,
        overOdds: null,
        underOdds: null,
        fetchedAt: '2026-04-25T12:00:00.000Z',
        isBestLine: true,
        source: 'The Odds API',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-b04-koe-20260425',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.market).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
      lineMovementCount: 0,
      bookSnapshotCount: 1,
      bookmakerCount: 1,
    });
    expect(body.diagnostics.fallbackUsage.market).toMatchObject({
      lineMovementSource: 'none',
      bookSnapshotSource: 'exact',
      fallbackUsed: false,
    });
    expect(state.fallbackGameOddsParams).toBeNull();
  });

  it('drops stale external news even when the team names still match', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'nba-bos-lal-20260415',
      league: 'nba',
      home_team: 'Los Angeles Lakers',
      away_team: 'Boston Celtics',
      home_short: 'LAL',
      away_short: 'BOS',
      starts_at: '2026-04-15T23:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'nba',
      home_team: 'Los Angeles Lakers',
      away_team: 'Boston Celtics',
    };
    state.newsRows = [
      {
        id: 'ext-old',
        type: 'external',
        title: 'Boston Celtics at Los Angeles Lakers preview',
        description: 'Old matchup piece that should be ignored now.',
        source: 'AP',
        source_display: 'AP',
        sport: 'nba',
        published_at: '2026-04-09T14:00:00.000Z',
      },
      {
        id: 'ext-fresh',
        type: 'external',
        title: 'Lakers injury watch',
        description: 'Boston Celtics and Los Angeles Lakers both dealing with late lineup questions.',
        source: 'ESPN',
        source_display: 'ESPN',
        sport: 'nba',
        published_at: '2026-04-15T18:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'nba-bos-lal-20260415',
      summaryOnly: false,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(body.news.data.items).toHaveLength(1);
    expect(body.news.data.items[0].id).toBe('ext-fresh');
  });

  it('returns a failed news reason instead of hanging when the event news query times out', async () => {
    state.hangNewsQuery = true;

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'evt1',
      summaryOnly: true,
      signedIn: true,
    });

    expect(body.coverage.news).toMatchObject({
      available: false,
      status: 'failed',
      count: 0,
    });
    expect(body.coverage.newsCoverageReason).toBe('News timed out');
    expect(body.diagnostics.fallbackUsage.news).toMatchObject({
      source: 'timeout',
      fallbackUsed: false,
      itemCount: 0,
    });
  });

  it('reuses cached event news when the live query times out', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-04-15T18:10:00.000Z'));

    try {
    state.newsRows = [
      {
        id: 'news-1',
        title: 'Lakers vs Celtics injury watch',
        description: 'Los Angeles Lakers and Boston Celtics injury report update.',
        source: 'AP',
        source_display: 'AP',
        sport: 'nba',
        published_at: '2026-04-15T18:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const warmBody = await buildDigilanderBundleBody({
      eventId: 'evt1',
      summaryOnly: true,
      signedIn: true,
    });
    expect(warmBody.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(warmBody.diagnostics.fallbackUsage.news).toMatchObject({
      source: 'live',
      fallbackUsed: false,
      itemCount: 1,
    });

    vi.setSystemTime(new Date('2026-04-15T18:11:01.000Z'));
    state.hangNewsQuery = true;
    state.newsRows = [];

    const cachedBodyPromise = buildDigilanderBundleBody({
      eventId: 'evt1',
      summaryOnly: true,
      signedIn: true,
    });
    await vi.advanceTimersByTimeAsync(100);
    const cachedBody = await cachedBodyPromise;

    expect(cachedBody.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(cachedBody.coverage.newsCoverageReason).toBeNull();
    expect(cachedBody.diagnostics.fallbackUsage.news).toMatchObject({
      source: 'stale_cache',
      fallbackUsed: true,
      itemCount: 1,
    });
    } finally {
      vi.useRealTimers();
    }
  });

  it('keeps timely one-team soccer news when the event filter score is still positive', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-wob-fcu-20260418',
      league: 'bundesliga',
      home_team: '1. FC Union Berlin',
      away_team: 'VfL Wolfsburg',
      home_short: 'FCU',
      away_short: 'WOB',
      starts_at: '2026-04-18T13:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: '1. FC Union Berlin',
      away_team: 'VfL Wolfsburg',
    };
    state.newsRows = [
      {
        id: 'news-1',
        type: 'external',
        title: 'Union Berlin get injury boost before Saturday kickoff',
        description: 'Union Berlin could welcome a key starter back this weekend.',
        source: 'AP',
        source_display: 'AP',
        sport: 'soccer',
        published_at: '2026-04-18T08:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-wob-fcu-20260418',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(body.coverage.newsCoverageReason).toBeNull();
  });

  it('keeps soccer market snapshots available when line movement queries stall', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-04-18T11:00:00.000Z'));

    try {
    state.curated = {
      ...state.curated,
      event_id: 'epl-ful-bre-20260418',
      league: 'epl',
      home_team: 'Brentford',
      away_team: 'Fulham',
      home_short: 'BRE',
      away_short: 'FUL',
      starts_at: '2026-04-18T11:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'epl',
      home_team: 'Brentford',
      away_team: 'Fulham',
    };
    state.hangLineMovementQuery = true;
    state.exactGameOddsRows = [
      {
        bookmaker: 'DraftKings',
        market: 'h2h',
        lineValue: null,
        homeOdds: 120,
        awayOdds: 210,
        overOdds: null,
        underOdds: null,
        fetchedAt: '2026-04-18T10:55:00.000Z',
        isBestLine: true,
        source: 'The Odds API',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const bodyPromise = buildDigilanderBundleBody({
      eventId: 'epl-ful-bre-20260418',
      summaryOnly: true,
      signedIn: true,
    });
    await vi.advanceTimersByTimeAsync(100);
    const body = await bodyPromise;

    expect(body.coverage.market).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
      lineMovementCount: 0,
      bookSnapshotCount: 1,
      bookmakerCount: 1,
    });
    expect(body.diagnostics.fallbackUsage.market).toMatchObject({
      lineMovementSource: 'none',
      bookSnapshotSource: 'exact',
      fallbackUsed: false,
    });
    } finally {
      vi.useRealTimers();
    }
  });

  it('marks market data stale instead of treating it as missing', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-04-18T12:00:00.000Z'));

    try {
      state.exactLineMovementRows = [
        {
          marketType: 'moneyline',
          openLine: -110,
          currentLine: -105,
          closingLine: null,
          lineMovement: 5,
          movementDirection: 'toward_home',
          openOdds: -110,
          currentOdds: -105,
          closingOdds: null,
          steamMove: false,
          reverseLineMove: false,
          recordedAt: '2026-04-15T17:30:00.000Z',
        },
      ];
      state.exactGameOddsRows = [
        {
          bookmaker: 'DraftKings',
          market: 'h2h',
          lineValue: null,
          homeOdds: -105,
          awayOdds: 100,
          overOdds: null,
          underOdds: null,
          fetchedAt: '2026-04-15T18:05:00.000Z',
          isBestLine: true,
          source: 'The Odds API',
        },
      ];

      const buildDigilanderBundleBody = await loadBundleHelper();
      const body = await buildDigilanderBundleBody({ eventId: 'evt1', summaryOnly: false, signedIn: false });

      expect(body.coverage.market).toMatchObject({
        available: true,
        status: 'ready',
        count: 2,
        readiness: 'partial',
        coverageConfidence: 'medium',
        bookmakerCount: 1,
        freshness: 'stale',
        lastSnapshotAt: '2026-04-15T18:05:00.000Z',
      });
      expect(body.diagnostics.moduleReadiness.market).toMatchObject({
        readiness: 'partial',
        coverageConfidence: 'medium',
        signals: {
          lineMovementCount: 1,
          bookSnapshotCount: 1,
          freshness: 'stale',
        },
      });
      expect(body.market.data.summary).toMatchObject({
        bookmakerCount: 1,
        bookSnapshotCount: 1,
        lineMovementCount: 1,
        freshness: 'stale',
        lastSnapshotAt: '2026-04-15T18:05:00.000Z',
        bestBookNow: {
          bookmaker: 'DraftKings',
          marketType: 'moneyline',
          label: 'Moneyline',
        },
        lineChangedFrom: {
          marketType: 'moneyline',
          label: 'Moneyline',
          openLine: -110,
          currentLine: -105,
          delta: 5,
        },
      });
      expect(body.market.data.summary.cards[0]).toMatchObject({
        marketType: 'moneyline',
        label: 'Moneyline',
        bookmakerCount: 1,
        freshness: 'stale',
      });
    } finally {
      vi.useRealTimers();
    }
  });

  it('matches one-team Hamburg coverage for HSV through soccer aliases', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-hsv-svw-20260418',
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
      home_short: 'SVW',
      away_short: 'HSV',
      starts_at: '2026-04-18T13:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
    };
    state.newsRows = [
      {
        id: 'news-hsv-1',
        type: 'external',
        title: 'Hamburg injury update before Saturday trip',
        description: 'A key starter could return for the visitors.',
        source: 'AP',
        source_display: 'AP',
        sport: 'soccer',
        published_at: '2026-04-18T08:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-hsv-svw-20260418',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(body.coverage.newsCoverageReason).toBeNull();
  });

  it('matches Spurs and Wolves shorthand external coverage in EPL', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'epl-wol-tot-20260419',
      league: 'epl',
      home_team: 'Wolverhampton Wanderers',
      away_team: 'Tottenham Hotspur',
      home_short: 'WOL',
      away_short: 'TOT',
      starts_at: '2026-04-19T15:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'epl',
      home_team: 'Wolverhampton Wanderers',
      away_team: 'Tottenham Hotspur',
    };
    state.newsRows = [
      {
        id: 'news-epl-1',
        type: 'external',
        title: 'Spurs look to stay hot against Wolves',
        description: 'Sunday could shape up as a big test in the top-half race.',
        source: 'ESPN',
        source_display: 'ESPN',
        sport: 'soccer',
        published_at: '2026-04-19T09:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'epl-wol-tot-20260419',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(body.coverage.newsCoverageReason).toBeNull();
  });

  it('keeps older soccer club news inside the wider external lookback window', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'epl-tot-wol-20260425',
      league: 'epl',
      home_team: 'Wolverhampton Wanderers',
      away_team: 'Tottenham Hotspur',
      home_short: 'WOL',
      away_short: 'TOT',
      starts_at: '2026-04-25T14:00:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'epl',
      home_team: 'Wolverhampton Wanderers',
      away_team: 'Tottenham Hotspur',
    };
    state.newsRows = [
      {
        id: 'news-epl-old-1',
        type: 'external',
        title: 'Premier League overreactions: Can Arsenal, Spurs stop downward spiral?',
        description: 'Alarm bells are ringing at Tottenham after the latest weekend action.',
        source: 'ESPN',
        source_display: 'ESPN',
        sport: 'soccer',
        published_at: '2026-04-15T12:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'epl-tot-wol-20260425',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: true,
      status: 'ready',
      count: 1,
    });
    expect(body.coverage.newsCoverageReason).toBeNull();
  });

  it('drops weak older one-team soccer news even inside the wider lookback window', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-hsv-svw-20260418',
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
      home_short: 'SVW',
      away_short: 'HSV',
      starts_at: '2026-04-18T13:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
    };
    state.newsRows = [
      {
        id: 'news-weak-1',
        type: 'external',
        title: 'Bremen make routine training adjustments',
        description: 'The hosts continue preparations after last weekend.',
        source: 'AP',
        source_display: 'AP',
        sport: 'soccer',
        published_at: '2026-04-09T08:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-hsv-svw-20260418',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: false,
      status: 'empty',
      count: 0,
    });
    expect(body.coverage.newsCoverageReason).toBe('No matchup-specific news hits');
  });

  it('drops older soccer news that only matches through buried body text', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'bundesliga-hsv-svw-20260418',
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
      home_short: 'SVW',
      away_short: 'HSV',
      starts_at: '2026-04-18T13:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'bundesliga',
      home_team: 'SV Werder Bremen',
      away_team: 'Hamburger SV',
    };
    state.newsRows = [
      {
        id: 'news-hidden-1',
        type: 'external',
        title: 'Top talent draws transfer interest from Chelsea and Madrid',
        description: 'Werder Bremen remain in the conversation after this week of talks.',
        source: 'Yahoo',
        source_display: 'Yahoo',
        sport: 'soccer',
        published_at: '2026-04-09T08:00:00.000Z',
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'bundesliga-hsv-svw-20260418',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.news).toMatchObject({
      available: false,
      status: 'empty',
      count: 0,
    });
    expect(body.coverage.newsCoverageReason).toBe('No matchup-specific news hits');
  });

  it('uses cached team props for summary support before rebuilding market candidates', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'evt-cache-first',
      league: 'epl',
      home_team: 'Arsenal',
      away_team: 'Chelsea',
      home_short: 'ARS',
      away_short: 'CHE',
      starts_at: '2026-04-16T19:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'epl',
      home_team: 'Arsenal',
      away_team: 'Chelsea',
    };
    state.teamPropsCacheRows = [
      {
        team: 'home',
        props_data: {
          playerProps: [
            { player: 'Bukayo Saka', prop: 'Shots 2.5', recommendation: 'Over' },
          ],
        },
      },
      {
        team: 'away',
        props_data: {
          props: [
            { player: 'Cole Palmer', prop: 'Shots 2.5', recommendation: 'Over' },
          ],
        },
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'evt-cache-first',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.playerProps).toMatchObject({
      available: true,
      status: 'ready',
      count: 2,
      sourceCount: 2,
      normalizedCount: 0,
      fallbackCount: 2,
      exposedCount: 2,
      marketCount: 2,
      readiness: 'partial',
      coverageConfidence: 'medium',
    });
    expect(body.coverage.playerData).toMatchObject({
      available: true,
      status: 'ready',
      count: 4,
      playerCount: 2,
      lineBackedCount: 2,
    });
    expect(body.coverage.teamBreakdown).toMatchObject({
      available: true,
      status: 'ready',
      count: 2,
    });
    expect(body.diagnostics.fallbackUsage.summarySupport).toMatchObject({
      source: 'cached_team_props',
      fallbackUsed: true,
      playerCount: 2,
      linePropsCount: 0,
      marketPropsCount: 2,
    });
    expect(mocked.fetchTeamPropMarketCandidates).not.toHaveBeenCalled();
  });

  it('uses raw source rows for summary support before rebuilding market candidates', async () => {
    state.curated = {
      ...state.curated,
      event_id: 'evt-source-first',
      league: 'epl',
      home_team: 'Arsenal',
      away_team: 'Chelsea',
      home_short: 'ARS',
      away_short: 'CHE',
      starts_at: '2026-04-16T19:30:00.000Z',
    };
    state.cachedForecast = {
      ...state.cachedForecast,
      league: 'epl',
      home_team: 'Arsenal',
      away_team: 'Chelsea',
    };
    state.sourceSummaryRows = [
      {
        playerExternalId: 'Bukayo_Saka_123_ARS',
        propType: 'shots',
        lineValue: 2.5,
        market: 'game_player_shots_over',
        marketName: 'Bukayo Saka shots Over/Under',
        raw: { side: 'over' },
      },
      {
        playerExternalId: 'Bukayo_Saka_123_ARS',
        propType: 'shots',
        lineValue: 2.5,
        market: 'game_player_shots_under',
        marketName: 'Bukayo Saka shots Over/Under',
        raw: { side: 'under' },
      },
      {
        playerExternalId: null,
        propType: 'shots',
        lineValue: 1.5,
        market: 'game_player_shots_over',
        marketName: 'Cole Palmer shots Over/Under',
        raw: { side: 'over' },
      },
    ];

    const buildDigilanderBundleBody = await loadBundleHelper();
    const body = await buildDigilanderBundleBody({
      eventId: 'evt-source-first',
      summaryOnly: true,
      signedIn: false,
    });

    expect(body.coverage.playerProps).toMatchObject({
      available: true,
      status: 'ready',
      count: 3,
      sourceCount: 3,
      normalizedCount: 0,
      fallbackCount: 0,
      exposedCount: 3,
      marketCount: 3,
    });
    expect(body.coverage.playerData).toMatchObject({
      available: true,
      status: 'ready',
      count: 5,
      playerCount: 2,
      lineBackedCount: 3,
    });
    expect(body.coverage.teamBreakdown).toMatchObject({
      available: true,
      status: 'ready',
      count: 3,
    });
    expect(body.diagnostics.fallbackUsage.summarySupport).toMatchObject({
      source: 'source_team_props',
      fallbackUsed: false,
      playerCount: 2,
      linePropsCount: 0,
      marketPropsCount: 3,
    });
    expect(mocked.fetchTeamPropMarketCandidates).not.toHaveBeenCalled();
  });
});
