const { gradeMlbProp, calculateMlbEdge, isMlbStatType } = require('../../src/services/mlbGrader');
const { evaluateMlbKillConditions, classifyLineMove, checkWeather } = require('../../src/services/mlbKillConditions');
const { MLB_PARKS, getParkByTeam } = require('../../src/constants/mlbParks');

jest.mock('axios');
const axios = require('axios');

describe('mlbGrader', () => {
  describe('grade thresholds', () => {
    test('Grade A when edge >= 5%', () => {
      const result = gradeMlbProp({
        player: 'Aaron Judge',
        stat_type: 'home_runs',
        line: 0.5,
        direction: 'over',
        seasonAvg: 0.7,
        recentAvg: 0.8,
      });
      expect(result.grade).toBe('A');
      expect(result.edge_pct).toBeGreaterThanOrEqual(5);
    });

    test('Grade B when edge 3-4%', () => {
      // seasonAvg=5.15, line=5, direction=over => seasonEdge=(5.15-5)/5*100=3%
      // recentAvg=5.2, line=5 => recentEdge=(5.2-5)/5*100=4%
      // composite = 3*0.6 + 4*0.4 = 1.8+1.6 = 3.4
      const result = gradeMlbProp({
        player: 'Test Player',
        stat_type: 'strikeouts',
        line: 5,
        direction: 'over',
        seasonAvg: 5.15,
        recentAvg: 5.2,
      });
      expect(result.grade).toBe('B');
      expect(result.edge_pct).toBeGreaterThanOrEqual(3);
      expect(result.edge_pct).toBeLessThan(5);
    });

    test('Grade C when edge 1-2%', () => {
      // seasonAvg=5.05, line=5, direction=over => seasonEdge=1%
      // recentAvg=5.1 => recentEdge=2%
      // composite = 1*0.6 + 2*0.4 = 0.6+0.8 = 1.4
      const result = gradeMlbProp({
        player: 'Test Player',
        stat_type: 'hits',
        line: 5,
        direction: 'over',
        seasonAvg: 5.05,
        recentAvg: 5.1,
      });
      expect(result.grade).toBe('C');
      expect(result.edge_pct).toBeGreaterThanOrEqual(1);
      expect(result.edge_pct).toBeLessThan(3);
    });

    test('Grade D when negative edge', () => {
      const result = gradeMlbProp({
        player: 'Test Player',
        stat_type: 'hits',
        line: 2,
        direction: 'over',
        seasonAvg: 1.5,
        recentAvg: 1.3,
      });
      expect(result.grade).toBe('D');
      expect(result.edge_pct).toBeLessThan(1);
    });
  });

  describe('isMlbStatType', () => {
    test('returns true for valid hitting stat', () => {
      expect(isMlbStatType('hits')).toBe(true);
      expect(isMlbStatType('home_runs')).toBe(true);
      expect(isMlbStatType('stolen_bases')).toBe(true);
    });

    test('returns true for valid pitching stat', () => {
      expect(isMlbStatType('strikeouts')).toBe(true);
      expect(isMlbStatType('earned_runs')).toBe(true);
      expect(isMlbStatType('pitches_thrown')).toBe(true);
    });

    test('returns false for invalid stat type', () => {
      expect(isMlbStatType('three_pointers')).toBe(false);
      expect(isMlbStatType('touchdowns')).toBe(false);
      expect(isMlbStatType('')).toBe(false);
    });
  });

  describe('calculateMlbEdge', () => {
    test('calculates positive edge for over', () => {
      const edge = calculateMlbEdge(6, 5, 'over');
      expect(edge).toBe(20);
    });

    test('calculates positive edge for under', () => {
      const edge = calculateMlbEdge(4, 5, 'under');
      expect(edge).toBe(20);
    });

    test('returns 0 for null inputs', () => {
      expect(calculateMlbEdge(null, 5, 'over')).toBe(0);
      expect(calculateMlbEdge(5, null, 'over')).toBe(0);
    });
  });
});

describe('mlbKillConditions', () => {
  function makeContext(overrides = {}) {
    return {
      inLineup: true,
      pitcherScratched: false,
      weather: { wind_speed: 5, wind_direction: 'OUT', temp: 75, humidity: 50 },
      platoonDelta: 5,
      paVsHandedness: 100,
      lineMovement: 0,
      hoursFromOpen: 1,
      parkFactor: 1.0,
      rainProbability: 10,
      onInjuryReport: false,
      ...overrides,
    };
  }

  test('LINEUP_OUT triggers when player not in lineup', () => {
    const result = evaluateMlbKillConditions(makeContext({ inLineup: false }));
    expect(result.some(c => c.code === 'LINEUP_OUT')).toBe(true);
  });

  test('PITCHER_SCRATCH triggers when pitcher scratched', () => {
    const result = evaluateMlbKillConditions(makeContext({ pitcherScratched: true }));
    expect(result.some(c => c.code === 'PITCHER_SCRATCH')).toBe(true);
  });

  test('WIND_IN triggers at 15mph+ blowing in', () => {
    const result = evaluateMlbKillConditions(makeContext({
      weather: { wind_speed: 18, wind_direction: 'IN', temp: 75, humidity: 50 },
    }));
    expect(result.some(c => c.code === 'WIND_IN')).toBe(true);
  });

  test('PLATOON_DISADVANTAGE triggers when delta > 12%', () => {
    const result = evaluateMlbKillConditions(makeContext({ platoonDelta: 15 }));
    expect(result.some(c => c.code === 'PLATOON_DISADVANTAGE')).toBe(true);
  });

  test('SMALL_SAMPLE triggers under 50 PA', () => {
    const result = evaluateMlbKillConditions(makeContext({ paVsHandedness: 30 }));
    expect(result.some(c => c.code === 'SMALL_SAMPLE')).toBe(true);
  });

  test('LINE_MOVE_AGAINST triggers at 0.5+ movement', () => {
    const result = evaluateMlbKillConditions(makeContext({ lineMovement: 0.7, hoursFromOpen: 1 }));
    expect(result.some(c => c.code === 'LINE_MOVE_AGAINST')).toBe(true);
  });

  test('PARK_SUPPRESSOR triggers below 0.90', () => {
    const result = evaluateMlbKillConditions(makeContext({ parkFactor: 0.85 }));
    expect(result.some(c => c.code === 'PARK_SUPPRESSOR')).toBe(true);
  });

  test('WEATHER_RAIN triggers above 50% probability', () => {
    const result = evaluateMlbKillConditions(makeContext({ rainProbability: 65 }));
    expect(result.some(c => c.code === 'WEATHER_RAIN')).toBe(true);
  });

  test('INJURY_REPORT triggers when on injury report', () => {
    const result = evaluateMlbKillConditions(makeContext({ onInjuryReport: true }));
    expect(result.some(c => c.code === 'INJURY_REPORT')).toBe(true);
  });

  test('HUMIDITY_SUPPRESSOR triggers at humidity > 80% and temp < 60F', () => {
    const result = evaluateMlbKillConditions(makeContext({
      weather: { wind_speed: 5, wind_direction: 'OUT', temp: 55, humidity: 85 },
    }));
    expect(result.some(c => c.code === 'HUMIDITY_SUPPRESSOR')).toBe(true);
  });
});

describe('classifyLineMove', () => {
  test('returns sharp for movement within first 2 hours', () => {
    expect(classifyLineMove(0.7, 1)).toBe('sharp');
    expect(classifyLineMove(-0.5, 0.5)).toBe('sharp');
  });

  test('returns public for movement after 4 hours', () => {
    expect(classifyLineMove(0.6, 5)).toBe('public');
    expect(classifyLineMove(-0.8, 6)).toBe('public');
  });

  test('returns null for movement under 0.5', () => {
    expect(classifyLineMove(0.3, 1)).toBeNull();
  });
});

describe('checkWeather', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('falls back to open-meteo on api.weather.gov timeout', async () => {
    // Mock weather.gov to timeout
    axios.get.mockImplementation((url) => {
      if (url.includes('weather.gov')) {
        return Promise.reject(new Error('timeout of 3000ms exceeded'));
      }
      // open-meteo fallback
      return Promise.resolve({
        data: {
          hourly: {
            temperature_2m: Array(24).fill(72),
            relative_humidity_2m: Array(24).fill(50),
            wind_speed_10m: Array(24).fill(10),
            wind_direction_10m: Array(24).fill(180),
            precipitation_probability: Array(24).fill(20),
          },
        },
      });
    });

    const result = await checkWeather([40.8296, -73.9262], 3000);
    expect(result.wind_speed).toBe(10);
    expect(result.temp).toBe(72);
    // Verify weather.gov was attempted first
    expect(axios.get).toHaveBeenCalledWith(
      expect.stringContaining('weather.gov'),
      expect.any(Object)
    );
  });
});

describe('mlbParks', () => {
  test('has exactly 30 entries', () => {
    expect(Object.keys(MLB_PARKS).length).toBe(30);
  });

  test('getParkByTeam returns correct park for NYY', () => {
    const park = getParkByTeam('NYY');
    expect(park).not.toBeNull();
    expect(park.name).toBe('Yankee Stadium');
    expect(park.coords).toEqual([40.8296, -73.9262]);
  });

  test('getParkByTeam returns correct park for LAD', () => {
    const park = getParkByTeam('LAD');
    expect(park.name).toBe('Dodger Stadium');
  });

  test('getParkByTeam returns null for invalid team', () => {
    expect(getParkByTeam('XXX')).toBeNull();
  });

  test('every park has name, coords, and team', () => {
    for (const [key, park] of Object.entries(MLB_PARKS)) {
      expect(park.name).toBeDefined();
      expect(park.coords).toHaveLength(2);
      expect(park.team).toBeDefined();
    }
  });
});
