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

import {
  americanOddsToImpliedProbability,
  buildPlayerPropMetadata,
  buildPlayerPropSignal,
  groupQualifiedSignalsByPlayer,
} from '../player-prop-signals';

describe('player-prop-signals', () => {
  it('classifies FAIR, GOOD, and STRONG signals from market vs forecast probability', () => {
    expect(americanOddsToImpliedProbability(-110)).toBe(52.4);

    const average = buildPlayerPropSignal({
      league: 'mlb',
      player: 'Juan Soto',
      team: 'NYY',
      teamSide: 'away',
      prop: 'Walks',
      statType: 'batting_basesOnBalls',
      marketLine: 0.5,
      odds: -110,
      projectedProbability: 63,
      projectedOutcome: 0.8,
      recommendation: 'OVER',
      playerRole: 'batter',
    });

    const good = buildPlayerPropSignal({
      league: 'mlb',
      player: 'Aaron Judge',
      team: 'NYY',
      teamSide: 'home',
      prop: 'Hits',
      statType: 'batting_hits',
      marketLine: 1.5,
      odds: -110,
      projectedProbability: 68,
      projectedOutcome: 1.8,
      recommendation: 'OVER',
      playerRole: 'batter',
    });

    const strong = buildPlayerPropSignal({
      league: 'mlb',
      player: 'Gerrit Cole',
      team: 'NYY',
      teamSide: 'home',
      prop: 'Strikeouts',
      statType: 'pitching_strikeouts',
      marketLine: 7.5,
      odds: +120,
      projectedProbability: 79,
      projectedOutcome: 8.7,
      recommendation: 'OVER',
      playerRole: 'pitcher',
    });

    expect(average?.signalTier).toBe('FAIR');
    expect(average?.edgePct).toBe(10.6);
    expect(good?.signalTier).toBe('GOOD');
    expect(good?.edgePct).toBe(15.6);
    expect(good?.agreementSources).toEqual(['model', 'market']);
    expect(good?.agreementLabel).toBe('MEDIUM');
    expect(good?.marketQualityLabel).toBe('FAIR');
    expect(strong?.signalTier).toBe('STRONG');
    expect(strong?.edgePct).toBe(33.5);
    expect(strong?.agreementLabel).toBe('HIGH');
    expect(strong?.marketQualityLabel).toBe('FAIR');
  });

  it('filters out low-value signals and groups good/strong props by player', () => {
    const grouped = groupQualifiedSignalsByPlayer([
      buildPlayerPropSignal({
        player: 'Shohei Ohtani',
        team: 'LAD',
        teamSide: 'home',
        league: 'mlb',
        prop: 'Hits',
        statType: 'batting_hits',
        marketLine: 1.5,
        odds: -110,
        projectedProbability: 68,
        projectedOutcome: 1.8,
        recommendation: 'OVER',
        playerRole: 'batter',
      })!,
      buildPlayerPropSignal({
        player: 'Shohei Ohtani',
        team: 'LAD',
        teamSide: 'home',
        league: 'mlb',
        prop: 'Home Runs',
        statType: 'batting_homeRuns',
        marketLine: 0.5,
        odds: +175,
        projectedProbability: 55,
        projectedOutcome: 0.8,
        recommendation: 'OVER',
        playerRole: 'batter',
      })!,
    ]);

    expect(grouped).toHaveLength(1);
    expect(grouped[0]).toMatchObject({
      player: 'Shohei Ohtani',
      strongestSignal: 'GOOD',
    });
    expect(grouped[0].props).toHaveLength(2);
  });

  it('standardizes baseball points labels to runs', () => {
    const signal = buildPlayerPropSignal({
      player: 'Mookie Betts',
      team: 'LAD',
      teamSide: 'home',
      league: 'mlb',
      prop: 'Points',
      statType: 'points',
      marketLine: 1.5,
      odds: +120,
      projectedProbability: 70,
      projectedOutcome: 2.1,
      recommendation: 'OVER',
      playerRole: 'batter',
    });

    expect(signal?.tableRow.propType).toBe('Runs');
  });

  it('uses registry-backed labels for expanded nba prop types', () => {
    const signal = buildPlayerPropSignal({
      player: 'Desmond Bane',
      team: 'MEM',
      teamSide: 'home',
      league: 'nba',
      prop: 'Three Pointers Made Over 3.5',
      statType: 'three_pointers_made',
      normalizedStatType: 'threes',
      marketLine: 3.5,
      odds: -105,
      projectedProbability: 68,
      projectedOutcome: 4.4,
      recommendation: 'OVER',
      playerRole: 'batter',
    });

    expect(signal?.tableRow.propType).toBe('3PT Made');
  });

  it('does not surface a signal when odds are missing', () => {
    const signal = buildPlayerPropSignal({
      player: 'Jalen Brunson',
      team: 'NYK',
      teamSide: 'home',
      league: 'nba',
      prop: 'Points',
      statType: 'points',
      marketLine: 27.5,
      odds: null,
      projectedProbability: 67,
      projectedOutcome: 30.1,
      edgePct: 16.2,
      recommendation: 'OVER',
      playerRole: 'batter',
    });

    expect(signal).toBeNull();
  });

  it('preserves metadata for weak publishable props without surfacing them as ranked signals', () => {
    const metadata = buildPlayerPropMetadata({
      league: 'nba',
      player: 'Brandon Miller',
      team: 'CHA',
      teamSide: 'away',
      prop: 'Points Under 19.5',
      statType: 'points',
      marketLine: 19.5,
      odds: -128,
      projectedProbability: 58,
      projectedOutcome: 17.5,
      recommendation: 'UNDER',
      marketSource: 'fanduel',
    });
    const signal = buildPlayerPropSignal({
      league: 'nba',
      player: 'Brandon Miller',
      team: 'CHA',
      teamSide: 'away',
      prop: 'Points Under 19.5',
      statType: 'points',
      marketLine: 19.5,
      odds: -128,
      projectedProbability: 58,
      projectedOutcome: 17.5,
      recommendation: 'UNDER',
      marketSource: 'fanduel',
    });

    expect(metadata).toMatchObject({
      forecastDirection: 'UNDER',
      marketImpliedProbability: 56.1,
      edgePct: 1.9,
      signalTier: 'COIN_FLIP',
      tableRow: null,
      agreementLabel: 'LOW',
      marketQualityLabel: 'GOOD',
    });
    expect(signal).toBeNull();
  });

  it('keeps source-backed soccer props with flat edges as metadata-only rows', () => {
    const metadata = buildPlayerPropMetadata({
      league: 'epl',
      player: 'Cole Palmer',
      team: 'CHE',
      teamSide: 'away',
      prop: 'Shots Over 2.5',
      statType: 'shots',
      marketLine: 2.5,
      odds: -110,
      projectedProbability: 52,
      projectedOutcome: 2.6,
      recommendation: 'OVER',
      marketSource: 'theoddsapi',
      sourceBacked: true,
    });
    const signal = buildPlayerPropSignal({
      league: 'epl',
      player: 'Cole Palmer',
      team: 'CHE',
      teamSide: 'away',
      prop: 'Shots Over 2.5',
      statType: 'shots',
      marketLine: 2.5,
      odds: -110,
      projectedProbability: 52,
      projectedOutcome: 2.6,
      recommendation: 'OVER',
      marketSource: 'theoddsapi',
      sourceBacked: true,
    });

    expect(metadata).toMatchObject({
      forecastDirection: 'OVER',
      marketImpliedProbability: 52.4,
      edgePct: 0,
      signalTier: 'COIN_FLIP',
      tableRow: null,
      agreementLabel: 'LOW',
      marketQualityLabel: 'FAIR',
    });
    expect(signal).toBeNull();
  });

  it('adds phase agreement when real MLB phase support exists', () => {
    const signal = buildPlayerPropSignal({
      player: 'Shohei Ohtani',
      team: 'LAD',
      teamSide: 'home',
      league: 'mlb',
      prop: 'Hits',
      statType: 'batting_hits',
      marketLine: 1.5,
      odds: -110,
      projectedProbability: 68,
      projectedOutcome: 1.8,
      recommendation: 'OVER',
      playerRole: 'batter',
      modelContext: {
        phase_support_score: 0.81,
        phase_support_direction: 'over',
      },
    });

    expect(signal?.agreementSources).toEqual(['model', 'market', 'phase']);
    expect(signal?.agreementScore).toBeGreaterThan(65);
    expect(signal?.agreementLabel).toBe('MEDIUM');
  });

  it('uses market completeness as a real scoring input', () => {
    const complete = buildPlayerPropSignal({
      league: 'mlb',
      player: 'Aaron Judge',
      team: 'NYY',
      teamSide: 'home',
      prop: 'Hits',
      statType: 'batting_hits',
      marketLine: 1.5,
      odds: -110,
      projectedProbability: 68,
      projectedOutcome: 1.8,
      recommendation: 'OVER',
      playerRole: 'batter',
      marketCompletenessStatus: 'source_complete',
    });

    const incomplete = buildPlayerPropSignal({
      league: 'mlb',
      player: 'Aaron Judge',
      team: 'NYY',
      teamSide: 'home',
      prop: 'Hits',
      statType: 'batting_hits',
      marketLine: 1.5,
      odds: -110,
      projectedProbability: 68,
      projectedOutcome: 1.8,
      recommendation: 'OVER',
      playerRole: 'batter',
      marketCompletenessStatus: 'incomplete',
    });

    expect(complete?.marketQualityLabel).toBe('STRONG');
    expect(incomplete?.marketQualityLabel).toBe('WEAK');
    expect((complete?.marketQualityScore || 0)).toBeGreaterThan(incomplete?.marketQualityScore || 0);
  });

  it('suppresses trivial MLB stolen base unders', () => {
    const signal = buildPlayerPropSignal({
      league: 'mlb',
      player: 'Jose Ramirez',
      team: 'CLE',
      teamSide: 'home',
      prop: 'Stolen Bases Under 0.5',
      statType: 'batting_stolenbases',
      marketLine: 0.5,
      odds: -110,
      projectedProbability: 70,
      projectedOutcome: 0.1,
      recommendation: 'UNDER',
      playerRole: 'batter',
      marketCompletenessStatus: 'source_complete',
    });

    expect(signal).toBeNull();
  });

  it('suppresses low-line basketball unders that are mostly restriction noise', () => {
    const signal = buildPlayerPropSignal({
      league: 'nba',
      player: 'Scottie Barnes',
      team: 'TOR',
      teamSide: 'home',
      prop: 'Assists Under 1.5',
      statType: 'assists',
      marketLine: 1.5,
      odds: +115,
      projectedProbability: 62,
      projectedOutcome: 1.0,
      recommendation: 'UNDER',
      modelContext: {
        injury_context: 'Likely minutes-restricted based on market pricing. High uncertainty on whether he plays meaningful time.',
        projected_minutes: 12,
      },
    });

    expect(signal).toBeNull();
  });

  it('keeps strong basketball over props as overs instead of mutating them into fake unders', () => {
    const signal = buildPlayerPropSignal({
      league: 'nba',
      player: 'Trae Young',
      team: 'ATL',
      teamSide: 'home',
      prop: 'Assists Over 5.5',
      statType: 'assists',
      marketLine: 5.5,
      odds: -110,
      projectedProbability: 70,
      projectedOutcome: 8.2,
      recommendation: 'OVER',
    });

    expect(signal).not.toBeNull();
    expect(signal?.forecastDirection).toBe('OVER');
    expect(signal?.tableRow.forecastDirection).toBe('OVER');
    expect(signal?.signalTier).toBe('GOOD');
  });

  it('still suppresses weak nba over props with soft projected probability', () => {
    const signal = buildPlayerPropSignal({
      league: 'nba',
      player: 'Trae Young',
      team: 'ATL',
      teamSide: 'home',
      prop: 'Assists Over 9.5',
      statType: 'assists',
      marketLine: 9.5,
      odds: +120,
      projectedProbability: 64,
      projectedOutcome: 10.1,
      recommendation: 'OVER',
    });

    expect(signal).toBeNull();
  });

  it('suppresses mainstream-looking but still low-information NBA under props', () => {
    const signal = buildPlayerPropSignal({
      league: 'nba',
      player: 'Derrick White',
      team: 'BOS',
      teamSide: 'away',
      prop: 'Points Under 15.5',
      statType: 'points',
      marketLine: 15.5,
      odds: -112,
      projectedProbability: 68,
      projectedOutcome: 13.8,
      recommendation: 'UNDER',
      modelContext: {
        season_average: 14.2,
        projected_minutes: 33,
      },
    });

    expect(signal).toBeNull();
  });

  it('suppresses low-line nhl shots on goal unders', () => {
    const signal = buildPlayerPropSignal({
      league: 'nhl',
      player: 'Brandon Hagel',
      team: 'TBL',
      teamSide: 'home',
      prop: 'Shots on Goal Under 2.0',
      statType: 'shots_onGoal',
      marketLine: 2,
      odds: -108,
      projectedProbability: 66,
      projectedOutcome: 1.5,
      recommendation: 'UNDER',
    });

    expect(signal).toBeNull();
  });

  it('suppresses suspicious longshot props with tiny projection margin', () => {
    const signal = buildPlayerPropSignal({
      league: 'nhl',
      player: 'Alex Newhook',
      team: 'MTL',
      teamSide: 'away',
      prop: 'Points Over 1.5',
      statType: 'points',
      marketLine: 1.5,
      odds: 4000,
      projectedProbability: 68.2,
      projectedOutcome: 1.6,
      recommendation: 'OVER',
    });

    expect(signal).toBeNull();
  });

  it('keeps legitimate longshot props when the projection margin is actually strong', () => {
    const signal = buildPlayerPropSignal({
      league: 'nhl',
      player: 'Alex Newhook',
      team: 'MTL',
      teamSide: 'away',
      prop: 'Points Over 1.5',
      statType: 'points',
      marketLine: 1.5,
      odds: 1400,
      projectedProbability: 68.2,
      projectedOutcome: 2.4,
      recommendation: 'OVER',
    });

    expect(signal).not.toBeNull();
    expect(signal?.forecastDirection).toBe('OVER');
  });
});
