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

const poolQuery = vi.fn();
const hasDvpData = vi.fn();
const hasHcwData = vi.fn();

vi.mock('../../db', () => ({ default: { query: poolQuery } }));
vi.mock('../../services/dvp', () => ({ hasDvpData }));
vi.mock('../../services/home-field-scout', () => ({ hasHcwData }));

describe('/free-pick compatibility', () => {
  beforeEach(() => {
    poolQuery.mockReset();
    hasDvpData.mockReset();
    hasHcwData.mockReset();
    vi.clearAllMocks();
    vi.resetModules();
    hasDvpData.mockResolvedValue(true);
    hasHcwData.mockReturnValue(true);
    process.env.PROP_DEDUP_ENABLED = 'false';
    process.env.PI_SIBLING_SUPPRESSION_ENABLED = 'false';
  });

  it('reads native model_signals shape without breaking the response', async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({ rows: [{ event_id: 'evt-1', starts_at: '2099-03-27T19:00:00.000Z', league: 'nba', forecast_data: {}, priced_prop_count: 1 }] })
      .mockResolvedValueOnce({
        rows: [{
          event_id: 'evt-1',
          league: 'nba',
          confidence_score: 0.72,
          home_team: 'Lakers',
          away_team: 'Celtics',
          starts_at: '2099-03-27T19:00:00.000Z',
          home_short: 'LAL',
          away_short: 'BOS',
          forecast_data: {},
          priced_prop_count: 1,
        }],
      })
      .mockResolvedValueOnce({
        rows: [{
          forecast_data: { winner_pick: 'Lakers', forecast_side: 'Lakers' },
          confidence_score: 0.72,
          composite_confidence: 0.76,
          model_signals: {
            compositeConfidence: 0.76,
            stormCategory: 4,
            signals: [],
            strategyProfile: { league: 'nba', signalWeights: {}, requiredSignals: [], optionalSignals: [], ragQueries: [] },
            ragInsights: [],
            edgeBreakdown: { grok: { score: 0.77, weight: 1, contribution: 0.77 } },
            inputQuality: { piff: 'A', dvp: 'B', digimon: 'N/A', odds: 'A', rag: 'A', overall: 'A' },
            compositeVersion: 'rm_2.0',
          },
          composite_version: 'rm_2.0',
          league: 'nba',
          home_team: 'Lakers',
          away_team: 'Celtics',
          odds_data: { moneyline: { home: -120, away: 110 }, spread: { home: { line: -4.5, odds: -110 }, away: { line: 4.5, odds: -110 } }, total: { over: { line: 220.5, odds: -110 }, under: { line: 220.5, odds: -110 } } },
          opening_moneyline: null,
          opening_spread: null,
          opening_total: null,
          created_at: '2026-03-27T19:00:00.000Z',
          home_short: 'LAL',
          away_short: 'BOS',
          moneyline: null,
          spread: null,
          total: null,
        }],
      })
      .mockResolvedValueOnce({
        rows: [{
          id: 'asset-1',
          player_name: 'LeBron James',
          team_id: 'LAL',
          team_side: 'home',
          forecast_payload: { prop: 'Points Over 27.5', odds: -110 },
        }],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/content');

    expect(res.status).toBe(200);
    expect(res.body.modelSignals).toMatchObject({
      grok: { confidence: 0.77, valueRating: 5 },
      piff: null,
    });
    expect(res.body.insightAvailability).toEqual({
      steam: false,
      sharp: false,
      dvp: true,
      hcw: true,
    });
    expect(res.body.playerPropsMode).toBe('per_player');
    expect(res.body.playerPropsAssets).toEqual([{
      assetId: 'asset-1',
      player: 'LeBron James',
      team: 'LAL',
      teamSide: 'home',
      prop: 'Points Over 27.5',
      locked: true,
    }]);
  });

  it('exposes the MLB engine path on free-pick content responses', async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({ rows: [{ event_id: 'evt-mlb', starts_at: '2099-03-27T19:00:00.000Z', league: 'mlb', forecast_data: {}, priced_prop_count: 0 }] })
      .mockResolvedValueOnce({
        rows: [{
          event_id: 'evt-mlb',
          league: 'mlb',
          confidence_score: 0.68,
          home_team: 'Astros',
          away_team: 'Rockies',
          starts_at: '2099-03-27T19:00:00.000Z',
          home_short: 'HOU',
          away_short: 'COL',
          forecast_data: { winner_pick: 'Astros' },
          priced_prop_count: 0,
        }],
      })
      .mockResolvedValueOnce({
        rows: [{
          forecast_data: { winner_pick: 'Astros', forecast_side: 'Astros', mlb_direct_model: { applied: true } },
          confidence_score: 0.68,
          composite_confidence: 0.71,
          model_signals: null,
          composite_version: 'rm_2.0',
          league: 'mlb',
          home_team: 'Astros',
          away_team: 'Rockies',
          odds_data: {},
          opening_moneyline: null,
          opening_spread: null,
          opening_total: null,
          created_at: '2026-03-27T19:00:00.000Z',
          home_short: 'HOU',
          away_short: 'COL',
          moneyline: null,
          spread: null,
          total: null,
        }],
      })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/content');

    expect(res.status).toBe(200);
    expect(res.body.teamForecastEngine).toEqual({
      code: 'mlb_direct_moneyline',
      label: 'MLB Direct Model',
      detail: 'Direct full-game moneyline lock passed the live abstain gate.',
      applied: true,
    });
  });

  it('filters free-pick prop previews to the same public-safe inventory', async () => {
    process.env.PROP_DEDUP_ENABLED = 'true';
    process.env.PI_SIBLING_SUPPRESSION_ENABLED = 'true';
    const { default: router } = await import('../free-pick');

    poolQuery.mockImplementation(async (sql: string) => {
      if (sql.includes('FROM rm_free_pick fp') && sql.includes('WHERE fp.pick_date = $1')) {
        return { rows: [{ event_id: 'evt-1', starts_at: '2099-03-27T19:00:00.000Z', league: 'nba', forecast_data: {}, priced_prop_count: 3 }] };
      }
      if (sql.includes('FROM rm_forecast_cache fc') && sql.includes('LIMIT 25')) {
        return {
          rows: [{
            event_id: 'evt-1',
            league: 'nba',
            confidence_score: 0.72,
            home_team: 'Lakers',
            away_team: 'Celtics',
            starts_at: '2099-03-27T19:00:00.000Z',
            home_short: 'LAL',
            away_short: 'BOS',
            forecast_data: {},
            priced_prop_count: 3,
          }],
        };
      }
      if (sql.includes('WHERE fc.event_id = $1')) {
        return {
          rows: [{
            forecast_data: { winner_pick: 'Lakers', forecast_side: 'Lakers' },
            confidence_score: 0.72,
            composite_confidence: 0.76,
            model_signals: null,
            composite_version: 'rm_2.0',
            league: 'nba',
            home_team: 'Lakers',
            away_team: 'Celtics',
            odds_data: {},
            opening_moneyline: null,
            opening_spread: null,
            opening_total: null,
            created_at: '2026-03-27T19:00:00.000Z',
            home_short: 'LAL',
            away_short: 'BOS',
            moneyline: null,
            spread: null,
            total: null,
          }],
        };
      }
      if (sql.includes('FROM rm_forecast_precomputed')) {
        return {
          rows: [
            { id: 'asset-keep', player_name: 'LeBron James', team_id: 'LAL', team_side: 'home', league: 'nba', confidence_score: 0.8, forecast_payload: { prop: 'Points Over 27.5', odds: -110, normalized_stat_type: 'points', market_line_value: 27.5, recommendation: 'over' } },
            { id: 'asset-out', player_name: 'Anthony Davis', team_id: 'LAL', team_side: 'home', league: 'nba', confidence_score: 0.79, forecast_payload: { prop: 'Rebounds Over 11.5', odds: -110, normalized_stat_type: 'rebounds', market_line_value: 11.5, recommendation: 'over' } },
            { id: 'asset-sibling', player_name: 'LeBron James', team_id: 'LAL', team_side: 'home', league: 'nba', confidence_score: 0.78, forecast_payload: { prop: 'Points Over 27.5', odds: -115, normalized_stat_type: 'points', market_line_value: 27.5, recommendation: 'over' } },
          ],
        };
      }
      if (sql.includes('FROM "PlayerInjury"')) {
        return { rows: [{ name: 'anthony davis' }] };
      }
      if (sql.includes('FROM pi_prop_eligibility')) {
        return { rows: [{ forecast_asset_id: 'asset-sibling' }] };
      }
      return { rows: [] };
    });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/content');

    expect(res.status).toBe(200);
    expect(res.body.playerPropsAssets).toEqual([{
      assetId: 'asset-keep',
      player: 'LeBron James',
      team: 'LAL',
      teamSide: 'home',
      prop: 'Points Over 27.5',
      locked: true,
    }]);
  });

  it('falls back to stale free-pick props when active inventory is empty', async () => {
    const { default: router } = await import('../free-pick');

    poolQuery.mockImplementation(async (sql: string) => {
      if (sql.includes('FROM rm_free_pick fp') && sql.includes('WHERE fp.pick_date = $1')) {
        return { rows: [{ event_id: 'evt-1', starts_at: '2099-03-27T19:00:00.000Z', league: 'nba', forecast_data: {}, priced_prop_count: 1 }] };
      }
      if (sql.includes('FROM rm_forecast_cache fc') && sql.includes('LIMIT 25')) {
        return {
          rows: [{
            event_id: 'evt-1',
            league: 'nba',
            confidence_score: 0.72,
            home_team: 'Lakers',
            away_team: 'Celtics',
            starts_at: '2099-03-27T19:00:00.000Z',
            home_short: 'LAL',
            away_short: 'BOS',
            forecast_data: {},
            priced_prop_count: 1,
          }],
        };
      }
      if (sql.includes('WHERE fc.event_id = $1')) {
        return {
          rows: [{
            forecast_data: { winner_pick: 'Lakers', forecast_side: 'Lakers' },
            confidence_score: 0.72,
            composite_confidence: 0.76,
            model_signals: null,
            composite_version: 'rm_2.0',
            league: 'nba',
            home_team: 'Lakers',
            away_team: 'Celtics',
            odds_data: {},
            opening_moneyline: null,
            opening_spread: null,
            opening_total: null,
            created_at: '2026-03-27T19:00:00.000Z',
            home_short: 'LAL',
            away_short: 'BOS',
            moneyline: null,
            spread: null,
            total: null,
          }],
        };
      }
      if (sql.includes('FROM rm_forecast_precomputed')) {
        return {
          rows: [
            { id: 'asset-stale', player_name: 'LeBron James', team_id: 'LAL', team_side: 'home', league: 'nba', status: 'STALE', confidence_score: 0.8, forecast_payload: { prop: 'Points Over 27.5', odds: -110, normalized_stat_type: 'points', market_line_value: 27.5, recommendation: 'over' } },
          ],
        };
      }
      if (sql.includes('FROM "PlayerInjury"')) {
        return { rows: [] };
      }
      return { rows: [] };
    });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/content');

    expect(res.status).toBe(200);
    expect(res.body.playerPropsAssets).toEqual([{
      assetId: 'asset-stale',
      player: 'LeBron James',
      team: 'LAL',
      teamSide: 'home',
      prop: 'Points Over 27.5',
      locked: true,
    }]);
  });

  it('prefers a richer free pick within the confidence band instead of a dead board', async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            event_id: 'evt-thin',
            league: 'nba',
            confidence_score: 0.72,
            home_team: 'Celtics',
            away_team: 'Knicks',
            starts_at: '2099-03-28T19:00:00.000Z',
            home_short: 'BOS',
            away_short: 'NYK',
            forecast_data: { winner_pick: 'Celtics' },
            priced_prop_count: 0,
          },
          {
            event_id: 'evt-rich',
            league: 'nhl',
            confidence_score: 0.69,
            home_team: 'Golden Knights',
            away_team: 'Capitals',
            starts_at: '2099-03-28T19:30:00.000Z',
            home_short: 'VGK',
            away_short: 'WSH',
            forecast_data: { winner_pick: 'Golden Knights' },
            priced_prop_count: 2,
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/');

    expect(res.status).toBe(200);
    expect(res.body.pick).toMatchObject({
      eventId: 'evt-rich',
      league: 'nhl',
      homeTeam: 'Golden Knights',
      awayTeam: 'Capitals',
    });
  });

  it('upgrades a saved weak free pick when a richer in-band candidate appears later', async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({
        rows: [{
          pick_date: '2099-03-28',
          event_id: 'evt-thin',
          league: 'nba',
          confidence_score: 0.72,
          matchup: 'Knicks @ Celtics',
          starts_at: '2099-03-28T19:00:00.000Z',
          home_team: 'Celtics',
          away_team: 'Knicks',
          home_short: 'BOS',
          away_short: 'NYK',
          forecast_data: { winner_pick: 'Celtics' },
          moneyline: null,
          spread: null,
          total: null,
          opening_moneyline: null,
          opening_spread: null,
          opening_total: null,
          priced_prop_count: 0,
        }],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            event_id: 'evt-thin',
            league: 'nba',
            confidence_score: 0.72,
            home_team: 'Celtics',
            away_team: 'Knicks',
            starts_at: '2099-03-28T19:00:00.000Z',
            home_short: 'BOS',
            away_short: 'NYK',
            forecast_data: { winner_pick: 'Celtics' },
            priced_prop_count: 0,
          },
          {
            event_id: 'evt-rich',
            league: 'nhl',
            confidence_score: 0.69,
            home_team: 'Golden Knights',
            away_team: 'Capitals',
            starts_at: '2099-03-28T19:30:00.000Z',
            home_short: 'VGK',
            away_short: 'WSH',
            forecast_data: { winner_pick: 'Golden Knights' },
            priced_prop_count: 2,
          },
        ],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            event_id: 'evt-thin',
            league: 'nba',
            confidence_score: 0.72,
            home_team: 'Celtics',
            away_team: 'Knicks',
            starts_at: '2099-03-28T19:00:00.000Z',
            home_short: 'BOS',
            away_short: 'NYK',
            forecast_data: { winner_pick: 'Celtics' },
            priced_prop_count: 0,
          },
          {
            event_id: 'evt-rich',
            league: 'nhl',
            confidence_score: 0.69,
            home_team: 'Golden Knights',
            away_team: 'Capitals',
            starts_at: '2099-03-28T19:30:00.000Z',
            home_short: 'VGK',
            away_short: 'WSH',
            forecast_data: { winner_pick: 'Golden Knights' },
            priced_prop_count: 2,
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/');

    expect(res.status).toBe(200);
    expect(res.body.pick).toMatchObject({
      eventId: 'evt-rich',
      league: 'nhl',
      homeTeam: 'Golden Knights',
      awayTeam: 'Capitals',
    });
  });


  it("backfills today's free pick from started games when no future games remain", async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [{
          event_id: 'evt-fallback-started',
          league: 'mlb',
          confidence_score: 0.74,
          home_team: 'Astros',
          away_team: 'Rockies',
          starts_at: '2000-03-28T19:00:00.000Z',
          forecast_data: { winner_pick: 'Astros', forecast_side: 'Astros', projected_winner: 'Astros' },
          home_short: 'HOU',
          away_short: 'COL',
          priced_prop_count: 2,
        }],
      })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/');

    expect(res.status).toBe(200);
    expect(res.body.pick).toMatchObject({
      eventId: 'evt-fallback-started',
      league: 'mlb',
      homeTeam: 'Astros',
      awayTeam: 'Rockies',
      winnerPick: 'Astros',
    });
  });

  it('keeps the saved free pick after start when no future replacement exists', async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({
        rows: [{
          pick_date: '2099-03-28',
          event_id: 'evt-started',
          league: 'nhl',
          confidence_score: 0.71,
          matchup: 'Capitals @ Golden Knights',
          starts_at: '2000-03-28T19:00:00.000Z',
          odds_updated_at: '2099-03-28T17:55:00.000Z',
          home_team: 'Golden Knights',
          away_team: 'Capitals',
          home_short: 'VGK',
          away_short: 'WSH',
          forecast_data: { winner_pick: 'Golden Knights', forecast_side: 'Golden Knights' },
          moneyline: { home: -125, away: 105 },
          spread: null,
          total: null,
          opening_moneyline: null,
          opening_spread: null,
          opening_total: null,
          priced_prop_count: 1,
        }],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/');

    expect(res.status).toBe(200);
    expect(res.body.pick).toMatchObject({
      eventId: 'evt-started',
      league: 'nhl',
      homeTeam: 'Golden Knights',
      awayTeam: 'Capitals',
      oddsUpdatedAt: '2099-03-28T17:55:00.000Z',
      winnerPick: 'Golden Knights',
    });
  });

  it('keeps serving saved free-pick content after start when no future replacement exists', async () => {
    const { default: router } = await import('../free-pick');
    poolQuery
      .mockResolvedValueOnce({
        rows: [{
          event_id: 'evt-started',
          starts_at: '2000-03-28T19:00:00.000Z',
          league: 'nhl',
          forecast_data: { winner_pick: 'Golden Knights', forecast_side: 'Golden Knights' },
          priced_prop_count: 1,
        }],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [{
          forecast_data: { winner_pick: 'Golden Knights', forecast_side: 'Golden Knights', summary: 'Lean Vegas.' },
          confidence_score: 0.71,
          composite_confidence: 0.74,
          model_signals: null,
          composite_version: 'rm_2.0',
          league: 'nhl',
          home_team: 'Golden Knights',
          away_team: 'Capitals',
          odds_data: { moneyline: { home: -125, away: 105 } },
          opening_moneyline: null,
          opening_spread: null,
          opening_total: null,
          created_at: '2026-03-28T22:00:00.000Z',
          home_short: 'VGK',
          away_short: 'WSH',
          moneyline: null,
          spread: null,
          total: null,
        }],
      })
      .mockResolvedValueOnce({ rows: [] });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/content');

    expect(res.status).toBe(200);
    expect(res.body).toMatchObject({
      eventId: 'evt-started',
      homeTeam: 'Golden Knights',
      awayTeam: 'Capitals',
      league: 'nhl',
    });
    expect(res.body.forecast).toMatchObject({
      winner_pick: 'Golden Knights',
    });
  });

  it('does not query the removed rm_events.odds_updated_at column', async () => {
    const { default: router } = await import('../free-pick');

    poolQuery.mockImplementation(async (sql: string) => {
      if (sql.includes('COALESCE(e.odds_updated_at')) {
        throw new Error('free-pick query regressed to removed e.odds_updated_at column');
      }

      if (sql.includes('FROM rm_free_pick fp') && sql.includes('WHERE fp.pick_date = $1')) {
        return {
          rows: [{
            pick_date: '2099-03-28',
            event_id: 'evt-safe',
            league: 'nba',
            confidence_score: 0.7,
            matchup: 'Knicks @ Celtics',
            starts_at: '2099-03-28T19:00:00.000Z',
            odds_updated_at: '2099-03-28T18:00:00.000Z',
            home_team: 'Celtics',
            away_team: 'Knicks',
            home_short: 'BOS',
            away_short: 'NYK',
            forecast_data: { winner_pick: 'Celtics', forecast_side: 'Celtics' },
            moneyline: { home: -120, away: 100 },
            spread: null,
            total: null,
            opening_moneyline: null,
            opening_spread: null,
            opening_total: null,
            priced_prop_count: 1,
          }],
        };
      }

      return { rows: [] };
    });

    const app = express();
    app.use('/', router);
    const res = await request(app).get('/');

    expect(res.status).toBe(200);
    expect(res.body.pick).toMatchObject({
      eventId: 'evt-safe',
      oddsUpdatedAt: '2099-03-28T18:00:00.000Z',
    });
  });
});
