import { describe, expect, it } from 'vitest';
import {
  buildForecastFeedAuditReport,
  filterRainmakerFeedByEtDate,
  flattenRainmakerEventsFeed,
  matchRainmakerEvent,
  type RainmakerPublicEventsFeed,
  type RainmakerPublicEvent,
  type TheOddsCurrentEvent,
} from '../forecast-feed-audit';

function makeRainmakerEvent(overrides: Partial<RainmakerPublicEvent> = {}): RainmakerPublicEvent {
  return {
    id: 'nba-phx-orl-20260331',
    league: 'nba',
    homeTeam: 'Orlando Magic',
    awayTeam: 'Phoenix Suns',
    startsAt: '2026-03-31T23:00:00.000Z',
    moneyline: { home: -130, away: 106 },
    spread: {
      home: { line: -2, odds: -110 },
      away: { line: 2, odds: -110 },
    },
    total: {
      over: { line: 224.5, odds: -111 },
      under: { line: 224.5, odds: -110 },
    },
    forecastStatus: 'ready',
    forecastMeta: {
      confidence: 0.551,
      forecastSide: 'Orlando Magic',
    },
    ...overrides,
  };
}

function makeFeed(event: RainmakerPublicEvent): RainmakerPublicEventsFeed {
  return {
    totalEvents: 1,
    leagues: [event.league],
    events: {
      [event.league]: [event],
    },
  };
}

function makeThirdPartyEvent(overrides: Partial<TheOddsCurrentEvent> = {}): TheOddsCurrentEvent {
  return {
    id: 'third-party-1',
    sport_key: 'basketball_nba',
    sport_title: 'NBA',
    commence_time: '2026-03-31T23:00:00Z',
    home_team: 'Orlando Magic',
    away_team: 'Phoenix Suns',
    bookmakers: [
      {
        key: 'fanduel',
        title: 'FanDuel',
        markets: [
          {
            key: 'h2h',
            outcomes: [
              { name: 'Orlando Magic', price: -130 },
              { name: 'Phoenix Suns', price: 105 },
            ],
          },
          {
            key: 'spreads',
            outcomes: [
              { name: 'Orlando Magic', price: -110, point: -2 },
              { name: 'Phoenix Suns', price: -110, point: 2 },
            ],
          },
          {
            key: 'totals',
            outcomes: [
              { name: 'Over', price: -110, point: 224.5 },
              { name: 'Under', price: -110, point: 224.5 },
            ],
          },
        ],
      },
      {
        key: 'draftkings',
        title: 'DraftKings',
        markets: [
          {
            key: 'h2h',
            outcomes: [
              { name: 'Orlando Magic', price: -128 },
              { name: 'Phoenix Suns', price: 106 },
            ],
          },
          {
            key: 'spreads',
            outcomes: [
              { name: 'Orlando Magic', price: -108, point: -1.5 },
              { name: 'Phoenix Suns', price: -112, point: 1.5 },
            ],
          },
          {
            key: 'totals',
            outcomes: [
              { name: 'Over', price: -111, point: 224.5 },
              { name: 'Under', price: -109, point: 224.5 },
            ],
          },
        ],
      },
    ],
    ...overrides,
  };
}

describe('forecast-feed-audit', () => {
  it('flattens grouped Rainmaker events without dropping league context', () => {
    const event = makeRainmakerEvent({ league: 'nhl' });
    const flattened = flattenRainmakerEventsFeed(makeFeed(event));

    expect(flattened).toHaveLength(1);
    expect(flattened[0].league).toBe('nhl');
  });

  it('matches the closest third-party event by teams and start time', () => {
    const rainmakerEvent = makeRainmakerEvent();
    const exact = makeThirdPartyEvent({ id: 'exact' });
    const farther = makeThirdPartyEvent({ id: 'farther', commence_time: '2026-04-01T00:00:00Z' });

    const matched = matchRainmakerEvent(rainmakerEvent, [farther, exact]);

    expect(matched?.event.id).toBe('exact');
  });

  it('filters the public feed to a single ET date', () => {
    const todayEvent = makeRainmakerEvent({ id: 'today-event' });
    const tomorrowEvent = makeRainmakerEvent({
      id: 'tomorrow-event',
      startsAt: '2026-04-01T23:00:00.000Z',
    });

    const filtered = filterRainmakerFeedByEtDate({
      totalEvents: 2,
      leagues: ['nba'],
      events: {
        nba: [todayEvent, tomorrowEvent],
      },
    }, '2026-03-31');

    expect(filtered.totalEvents).toBe(1);
    expect(filtered.events.nba).toHaveLength(1);
    expect(filtered.events.nba[0].id).toBe('today-event');
  });

  it('matches aliased club names across vendor feeds', () => {
    const matched = matchRainmakerEvent(
      makeRainmakerEvent({
        league: 'bundesliga',
        id: 'bundesliga-fcb-scf-20260404',
        homeTeam: 'SC Freiburg',
        awayTeam: 'FC Bayern Munchen',
        startsAt: '2026-04-04T13:30:00.000Z',
      }),
      [makeThirdPartyEvent({
        id: 'alias-match',
        sport_key: 'soccer_germany_bundesliga',
        sport_title: 'Bundesliga',
        home_team: 'Freiburg',
        away_team: 'Bayern Munich',
        commence_time: '2026-04-04T13:30:00Z',
      })],
    );

    expect(matched?.event.id).toBe('alias-match');
  });

  it('reports start-time drift and ready forecasts with null confidence', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent({
        forecastMeta: {
          confidence: null,
          forecastSide: 'Orlando Magic',
        },
      })),
      thirdPartyByLeague: {
        nba: [makeThirdPartyEvent({ commence_time: '2026-03-31T23:10:00Z' })],
      },
      auditedAt: '2026-03-31T15:13:24.506Z',
    });

    expect(report.passed).toBe(false);
    expect(report.summary.readyWithoutConfidence).toBe(1);
    expect(report.summary.startTimeMismatches).toBe(1);
    expect(report.issueCounts.ready_without_confidence).toBe(1);
    expect(report.issueCounts.start_time_mismatch).toBe(1);
    expect(report.issueEventCount).toBe(1);
    expect(report.events[0].issues).toContain('ready_without_confidence');
    expect(report.events[0].issues).toContain('start_time_mismatch');
    expect(report.events[0].startDeltaMin).toBe(10);
  });

  it('flags market values that fall outside the third-party range', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent({
        moneyline: { home: -130, away: 140 },
      })),
      thirdPartyByLeague: {
        nba: [makeThirdPartyEvent({
          bookmakers: [
            {
              key: 'fanduel',
              title: 'FanDuel',
              markets: [
                {
                  key: 'h2h',
                  outcomes: [
                    { name: 'Orlando Magic', price: -130 },
                    { name: 'Phoenix Suns', price: 105 },
                  ],
                },
              ],
            },
            {
              key: 'draftkings',
              title: 'DraftKings',
              markets: [
                {
                  key: 'h2h',
                  outcomes: [
                    { name: 'Orlando Magic', price: -128 },
                    { name: 'Phoenix Suns', price: 106 },
                  ],
                },
              ],
            },
            {
              key: 'betmgm',
              title: 'BetMGM',
              markets: [
                {
                  key: 'h2h',
                  outcomes: [
                    { name: 'Orlando Magic', price: -129 },
                    { name: 'Phoenix Suns', price: 108 },
                  ],
                },
              ],
            },
          ],
        })],
      },
    });

    expect(report.passed).toBe(false);
    expect(report.summary.moneylineSidesOutsideRange).toBe(1);
    expect(report.issueCounts.moneyline_out_of_range).toBe(1);
    expect(report.events[0].issues).toContain('moneyline_out_of_range');
  });

  it('passes when markets are within range and no operational issues exist', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent()),
      thirdPartyByLeague: {
        nba: [makeThirdPartyEvent()],
      },
    });

    expect(report.passed).toBe(true);
    expect(report.issueEventCount).toBe(0);
    expect(report.summary.matchedEvents).toBe(1);
    expect(report.summary.moneylineSidesOutsideRange).toBe(0);
    expect(report.summary.spreadSidesOutsideRange).toBe(0);
    expect(report.summary.totalSidesOutsideRange).toBe(0);
    expect(report.exactRollups.exactSpreadLineSides).toBe(2);
    expect(report.exactRollups.exactTotalLineSides).toBe(2);
  });

  it('tolerates small american-odds drift in live market checks', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent({
        total: {
          over: { line: 224.5, odds: -115 },
          under: { line: 224.5, odds: -105 },
        },
      })),
      thirdPartyByLeague: {
        nba: [makeThirdPartyEvent({
          bookmakers: [
            {
              key: 'fanduel',
              title: 'FanDuel',
              markets: [
                {
                  key: 'totals',
                  outcomes: [
                    { name: 'Over', price: -110, point: 224.5 },
                    { name: 'Under', price: -110, point: 224.5 },
                  ],
                },
              ],
            },
          ],
        })],
      },
    });

    expect(report.summary.totalSidesOutsideRange).toBe(0);
    expect(report.issueCounts.total_out_of_range).toBe(0);
    expect(report.events[0].issues).not.toContain('total_out_of_range');
  });

  it('does not fail range checks when the third-party market sample is too thin', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent({
        league: 'epl',
        id: 'epl-ful-liv-20260411',
        homeTeam: 'Liverpool',
        awayTeam: 'Fulham',
        startsAt: '2026-04-11T14:00:00.000Z',
        total: {
          over: { line: 3.5, odds: 127 },
          under: { line: 3.5, odds: -167 },
        },
      })),
      thirdPartyByLeague: {
        epl: [makeThirdPartyEvent({
          sport_key: 'soccer_epl',
          sport_title: 'EPL',
          home_team: 'Liverpool',
          away_team: 'Fulham',
          commence_time: '2026-04-11T14:00:00Z',
          bookmakers: [
            {
              key: 'fanduel',
              title: 'FanDuel',
              markets: [
                {
                  key: 'totals',
                  outcomes: [
                    { name: 'Over', price: 120, point: 3.5 },
                    { name: 'Under', price: -165, point: 3.5 },
                  ],
                },
              ],
            },
          ],
        })],
      },
    });

    expect(report.summary.totalSidesOutsideRange).toBe(0);
    expect(report.issueCounts.total_out_of_range).toBe(0);
    expect(report.events[0].issues).not.toContain('total_out_of_range');
  });

  it('does not flag unmatched events that sit beyond the third-party schedule horizon', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent({
        league: 'champions_league',
        id: 'champions_league-psg-liv-20260414',
        homeTeam: 'Liverpool',
        awayTeam: 'Paris Saint-Germain',
        startsAt: '2026-04-14T19:00:00.000Z',
      })),
      thirdPartyByLeague: {
        champions_league: [makeThirdPartyEvent({
          sport_key: 'soccer_uefa_champs_league',
          sport_title: 'Champions League',
          home_team: 'Barcelona',
          away_team: 'Atletico Madrid',
          commence_time: '2026-04-08T19:00:00Z',
        })],
      },
    });

    expect(report.summary.unmatchedEvents).toBe(0);
    expect(report.issueCounts.event_unmatched).toBe(0);
    expect(report.events[0].issues).not.toContain('event_unmatched');
  });

  it('compares spread and total odds only against third-party prices at the same line', () => {
    const report = buildForecastFeedAuditReport({
      feed: makeFeed(makeRainmakerEvent({
        league: 'nhl',
        id: 'nhl-nj-nyr-20260331',
        homeTeam: 'New York Rangers',
        awayTeam: 'New Jersey Devils',
        startsAt: '2026-03-31T23:00:00.000Z',
        total: {
          over: { line: 6, odds: -115 },
          under: { line: 6, odds: -105 },
        },
      })),
      thirdPartyByLeague: {
        nhl: [makeThirdPartyEvent({
          sport_key: 'icehockey_nhl',
          sport_title: 'NHL',
          home_team: 'New York Rangers',
          away_team: 'New Jersey Devils',
          bookmakers: [
            {
              key: 'fanduel',
              title: 'FanDuel',
              markets: [
                {
                  key: 'totals',
                  outcomes: [
                    { name: 'Over', price: -115, point: 6 },
                    { name: 'Under', price: -105, point: 6 },
                  ],
                },
              ],
            },
            {
              key: 'draftkings',
              title: 'DraftKings',
              markets: [
                {
                  key: 'totals',
                  outcomes: [
                    { name: 'Over', price: 114, point: 6.5 },
                    { name: 'Under', price: -135, point: 6.5 },
                  ],
                },
              ],
            },
          ],
        })],
      },
    });

    expect(report.summary.totalSidesOutsideRange).toBe(0);
    expect(report.issueCounts.total_out_of_range).toBe(0);
    expect(report.events[0].issues).not.toContain('total_out_of_range');
  });
});
