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

const mocked = vi.hoisted(() => ({
  poolQuery: vi.fn(),
}));

vi.mock('../../db', () => ({ default: { query: mocked.poolQuery } }));

import { __mlbPropInternals } from '../grok';

describe('MLB publish scoring', () => {
  beforeEach(() => {
    mocked.poolQuery.mockReset();
  });

  it('penalizes missing confirmed lineups without zeroing all hitter props', () => {
    const [candidate] = __mlbPropInternals.scoreMlbCandidatesForPublishing([
      {
        player: 'Shohei Ohtani',
        statType: 'hits',
        marketLineValue: 1.5,
        recommendationHint: 'over',
        edge: null,
        prob: null,
        source: 'player_prop_line',
        propLabel: 'Hits Over 1.5',
        overOdds: -110,
        underOdds: -110,
      },
    ], {
      rawData: {
        firstFive: {
          homeStarter: null,
          awayStarter: { fip: 5.2, xfip: 5.1, kBbPct: 8, whip: 1.45 },
          homeOffense: { projectedWrcPlus: 121 },
          awayOffense: { projectedWrcPlus: 94 },
        },
        bullpen: {
          homeBullpen: { fip: 4.4, xfip: 4.3, kPct: 23, whip: 1.31, workload: { fatigueScore: 0.45 } },
          awayBullpen: { fip: 4.9, xfip: 4.8, kPct: 20, whip: 1.39, workload: { fatigueScore: 0.72 } },
        },
        context: {
          lineups: null,
          weatherRunBias: 0.08,
        },
      },
    }, 'home');

    expect(candidate.suppressionReason).toBeNull();
    expect(candidate.recommendationHint).toBe('over');
    expect(candidate.publishScore).toBeGreaterThan(0.54);
  });

  it('suppresses low-information hitter under 0.5 MLB candidates while preserving shortage-side metadata', () => {
    const [candidate] = __mlbPropInternals.scoreMlbCandidatesForPublishing([
      {
        player: 'Carson Williams',
        statType: 'batting_hits',
        marketLineValue: 0.5,
        recommendationHint: 'under',
        edge: 0.22,
        prob: 0.72,
        source: 'player_prop_line',
        propLabel: 'Hits Under 0.5',
        overOdds: 210,
        underOdds: -290,
      },
    ], {
      rawData: {
        firstFive: {
          homeStarter: { fip: 3.8, xfip: 3.9, kBbPct: 16, whip: 1.18 },
          awayStarter: { fip: 4.4, xfip: 4.3, kBbPct: 11, whip: 1.32 },
          homeOffense: { projectedWrcPlus: 104 },
          awayOffense: { projectedWrcPlus: 92 },
        },
        bullpen: {
          homeBullpen: { fip: 3.4, xfip: 3.6, kPct: 25, whip: 1.14, workload: { fatigueScore: 0.38 } },
          awayBullpen: { fip: 4.2, xfip: 4.1, kPct: 22, whip: 1.28, workload: { fatigueScore: 0.51 } },
        },
        context: {
          lineups: { homeStatus: 'confirmed', awayStatus: 'confirmed' },
          weatherRunBias: -0.03,
        },
      },
    }, 'away');

    expect(candidate.suppressionReason).toBe('low-information binary under');
    expect(candidate.recommendationHint).toBe('under');
  });

  it('penalizes one-sided hitter under markets while preserving shortage-side metadata', () => {
    const [candidate] = __mlbPropInternals.scoreMlbCandidatesForPublishing([
      {
        player: 'Chandler Simpson',
        statType: 'batting_totalBases',
        marketLineValue: 1.5,
        recommendationHint: 'under',
        edge: null,
        prob: null,
        source: 'player_prop_line',
        propLabel: 'Total Bases Under 1.5',
        overOdds: null,
        underOdds: -425,
      },
    ], {
      rawData: {
        firstFive: {
          homeStarter: { fip: 3.8, xfip: 3.9, kBbPct: 16, whip: 1.18 },
          awayStarter: { fip: 4.4, xfip: 4.3, kBbPct: 11, whip: 1.32 },
          homeOffense: { projectedWrcPlus: 104 },
          awayOffense: { projectedWrcPlus: 92 },
        },
        bullpen: {
          homeBullpen: { fip: 3.4, xfip: 3.6, kPct: 25, whip: 1.14, workload: { fatigueScore: 0.38 } },
          awayBullpen: { fip: 4.2, xfip: 4.1, kPct: 22, whip: 1.28, workload: { fatigueScore: 0.51 } },
        },
        context: {
          lineups: { homeStatus: 'confirmed', awayStatus: 'confirmed' },
          weatherRunBias: -0.03,
        },
      },
    }, 'away');

    expect(candidate.suppressionReason).toBe('low-information one-sided hitter under');
    expect(candidate.recommendationHint).toBe('under');
  });

  it('uses one-sided pitcher price support to keep meaningful under markets alive', () => {
    const [candidate] = __mlbPropInternals.scoreMlbCandidatesForPublishing([
      {
        player: 'Joe Boyle',
        statType: 'pitching_basesOnBalls',
        marketLineValue: 2.5,
        recommendationHint: 'under',
        edge: null,
        prob: null,
        source: 'player_prop_line',
        propLabel: 'Pitching Walks Allowed Under 2.5',
        overOdds: null,
        underOdds: -138,
      },
    ], {
      rawData: {
        firstFive: {
          homeStarter: { fip: 3.8, xfip: 3.9, kBbPct: 16, whip: 1.18 },
          awayStarter: { fip: 4.4, xfip: 4.3, kBbPct: 11, whip: 1.32 },
          homeOffense: { projectedWrcPlus: 104 },
          awayOffense: { projectedWrcPlus: 92 },
        },
        bullpen: {
          homeBullpen: { fip: 3.4, xfip: 3.6, kPct: 25, whip: 1.14, workload: { fatigueScore: 0.38 } },
          awayBullpen: { fip: 4.2, xfip: 4.1, kPct: 22, whip: 1.28, workload: { fatigueScore: 0.51 } },
        },
        context: {
          lineups: { homeStatus: 'confirmed', awayStatus: 'confirmed' },
          weatherRunBias: -0.03,
        },
      },
    }, 'away');

    expect(candidate.suppressionReason).toBeNull();
    expect(candidate.recommendationHint).toBe('under');
    expect(candidate.publishScore).toBeGreaterThanOrEqual(0.54);
  });

  it('enriches MLB team props with park, lineup, weather, and handedness context', async () => {
    mocked.poolQuery.mockImplementation(async (sql: string) => {
      if (sql.includes('FROM "PitcherSeason"')) {
        return { rows: [{ throws: 'L' }] };
      }
      if (sql.includes('FROM "HitterSeason"')) {
        return { rows: [{ bats: 'R' }] };
      }
      return { rows: [] };
    });

    const [candidate] = __mlbPropInternals.scoreMlbCandidatesForPublishing([
      {
        player: 'Shohei Ohtani',
        statType: 'homeruns',
        marketLineValue: 0.5,
        recommendationHint: 'over',
        edge: null,
        prob: null,
        source: 'player_prop_line',
        propLabel: 'Home Runs Over 0.5',
        overOdds: 165,
        underOdds: -210,
      },
    ], {
      rawData: {
        firstFive: {
          homeStarter: { name: 'Yoshinobu Yamamoto', kBbPct: 23.2 },
          awayStarter: { name: 'Eduardo Rodriguez', kBbPct: 12.4 },
        },
        bullpen: {},
        context: {
          lineups: { homeStatus: 'confirmed', awayStatus: 'projected' },
          weather: { temperatureF: 78, windMph: 11, conditions: 'Clear' },
          weatherRunBias: 0.09,
          venue: { indoor: false },
        },
      },
    }, 'home');

    const [prop] = await __mlbPropInternals.enrichMlbPropsWithContext([
      {
        player: 'Shohei Ohtani',
        prop: 'Home Runs Over 0.5',
        recommendation: 'over',
        reasoning: 'stub',
        stat_type: 'homeruns',
        market_line_value: 0.5,
        model_context: { context_summary: 'Power spot.' },
      },
    ], [candidate], {
      rawData: {
        firstFive: {
          homeStarter: { name: 'Yoshinobu Yamamoto', kBbPct: 23.2 },
          awayStarter: { name: 'Eduardo Rodriguez', kBbPct: 12.4 },
        },
        context: {
          lineups: { homeStatus: 'confirmed', awayStatus: 'projected' },
          weather: { temperatureF: 78, windMph: 11, conditions: 'Clear' },
          weatherRunBias: 0.09,
          venue: { indoor: false },
        },
      },
    }, {
      teamShort: 'LAD',
      opponentShort: 'ARI',
      homeShort: 'LAD',
      teamSide: 'home',
    });

    expect(prop.model_context?.park_factor).toBe(96);
    expect(prop.model_context?.lineup_certainty).toBe('confirmed');
    expect(prop.model_context?.weather_impact).toContain('positive');
    expect(prop.model_context?.handedness_split).toBe('platoon edge vs LHP');
    expect(prop.model_context?.context_summary).toContain('park 96');
    expect(prop.model_context?.context_summary).toContain('split platoon edge vs LHP');
  });

  it('keeps pitcher props when generated stat labels omit the pitching prefix', () => {
    const props = __mlbPropInternals.validateMlbPropsAgainstCandidates([
      {
        player: 'Tyler Glasnow',
        prop: 'Strikeouts Under 6.5',
        recommendation: 'under',
        reasoning: 'stub',
        stat_type: 'strikeouts',
        market_line_value: 6.5,
      },
    ], [
      {
        player: 'Tyler Glasnow',
        statType: 'pitchingstrikeouts',
        marketLineValue: 6.5,
        recommendationHint: 'under',
        edge: 0.11,
        prob: 0.61,
        source: 'player_prop_line',
        propLabel: 'Pitcher Strikeouts 6.5',
        overOdds: 110,
        underOdds: -140,
      },
    ]);

    expect(props).toHaveLength(1);
    expect(props[0].player).toBe('Tyler Glasnow');
  });
});
