/**
 * VYNDR Ship Build — Data Source Component Tests
 * Pure logic tests with inline data shapes and parsing logic.
 */

// ─── Inline Data Shapes ───────────────────────────────────────────────

const STARTING_TRUST = {
  beat_writer: 'reliable',
  national: 'authoritative',
  aggregator: 'unverified',
  insider: 'reliable',
};

const REPORTER_DATABASE = {
  nba: {
    shams_charania: { name: 'Shams Charania', source_type: 'insider', trust: 'reliable' },
    woj: { name: 'Adrian Wojnarowski', source_type: 'national', trust: 'authoritative' },
    local_beat: { name: 'Local Beat Writer', source_type: 'beat_writer', trust: 'reliable' },
  },
};

const TRUST_LEVELS = ['unverified', 'reliable', 'verified', 'authoritative'];

function escalateTrust(reporter) {
  const { tracked, accuracy } = reporter;
  if (tracked >= 30 && accuracy >= 0.95) return { level: 'authoritative', badge: 'confirmed' };
  if (tracked >= 20 && accuracy >= 0.90) return { level: 'verified', badge: 'trusted' };
  return { level: reporter.trust, badge: null };
}

// ─── Tweet Parsing ────────────────────────────────────────────────────

const STATUS_PATTERNS = [
  { pattern: /will start/i, status: 'confirmed_playing', confidence: 0.85 },
  { pattern: /scratched/i, status: 'scratched', confidence: 0.90 },
  { pattern: /game[- ]time decision/i, status: 'questionable', confidence: 0.70 },
  { pattern: /\bOUT\b/, status: 'out', confidence: 0.90 },
];

const PAST_TENSE_FILTERS = [/was out/i, /yesterday/i, /last night/i];

function parseTweet(text) {
  if (!text) return null;
  for (const filter of PAST_TENSE_FILTERS) {
    if (filter.test(text)) return null;
  }
  for (const { pattern, status, confidence } of STATUS_PATTERNS) {
    if (pattern.test(text)) return { status, confidence };
  }
  return null;
}

// ─── Odds API Parsing ─────────────────────────────────────────────────

function parseOddsOutcome(market, bookmakerKey) {
  if (!market || !market.outcomes || market.outcomes.length === 0) {
    return null;
  }
  const outcome = market.outcomes[0];
  return {
    player_name: outcome.name || null,
    line: outcome.point != null ? outcome.point : null,
    bookmaker: bookmakerKey,
  };
}

// ─── Line Movement Detection ──────────────────────────────────────────

function detectLineMovement(opening, current) {
  const diff = Math.abs(current - opening);
  if (diff < 0.5) return null;
  return {
    movement: diff,
    direction: current > opening ? 'up' : 'down',
    flagged: true,
  };
}

// ─── Weather + Dome Detection ─────────────────────────────────────────

const DOME_PARKS = ['tropicana_field', 'chase_field', 'minute_maid', 'rogers_centre', 'loanDepot_park', 'globe_life'];

function getWeatherForPark(park, conditions) {
  if (DOME_PARKS.includes(park)) {
    return { temperature: 72, wind: 0, humidity: 50, ball_carry_factor: 1.0 };
  }
  return conditions;
}

function ballCarryFactor(temperature, humidity) {
  let factor = 1.0;
  if (temperature > 72) factor += (temperature - 72) * 0.003;
  if (temperature < 72) factor -= (72 - temperature) * 0.003;
  if (humidity > 50) factor -= (humidity - 50) * 0.002;
  if (humidity < 50) factor += (50 - humidity) * 0.002;
  return parseFloat(factor.toFixed(4));
}

// ─── Catcher Framing ──────────────────────────────────────────────────

function clampFramingValue(raw) {
  return Math.max(-0.5, Math.min(0.5, raw));
}

// ─── Umpire / Referee Minimums ────────────────────────────────────────

function getUmpireAdjustment(umpire) {
  if (umpire.games < 30) return 0.0;
  return umpire.k_rate_delta;
}

function getRefereeAdjustment(referee) {
  if (referee.games < 30) return 0.0;
  return referee.foul_rate_delta;
}

// ─── MLB Lineup Parsing ──────────────────────────────────────────────

function parseLineupEntry(raw) {
  return {
    player: raw.player,
    batting_order: raw.batting_order,
    position: raw.position,
    status: raw.source === 'official_api' ? 'confirmed' : 'projected',
  };
}

// ─── ABS Challenge System ─────────────────────────────────────────────

function disciplineScore(chase_rate, bb_rate) {
  // chase_rate: 0-1 (lower = better), bb_rate: 0-1 (higher = better)
  const raw = (1 - chase_rate) * 0.5 + bb_rate * 0.5;
  return Math.max(0, Math.min(1, parseFloat(raw.toFixed(4))));
}

function absKAdjustment(discipline) {
  if (discipline > 0.7) return -0.05;
  return 0;
}

function framingVsDisciplined(framingValue, discipline) {
  // Framing is 50% less effective against disciplined batters
  if (discipline > 0.7) return framingValue * 0.5;
  return framingValue;
}

// ═══════════════════════════════════════════════════════════════════════
// TESTS
// ═══════════════════════════════════════════════════════════════════════

describe('Reporter seeding + trust', () => {
  test('beat_writer starts at reliable', () => {
    expect(STARTING_TRUST.beat_writer).toBe('reliable');
  });

  test('national starts at authoritative', () => {
    expect(STARTING_TRUST.national).toBe('authoritative');
  });

  test('aggregator starts at unverified', () => {
    expect(STARTING_TRUST.aggregator).toBe('unverified');
  });

  test('insider starts at reliable', () => {
    expect(STARTING_TRUST.insider).toBe('reliable');
  });

  test('reporter_database has nba key', () => {
    expect(REPORTER_DATABASE).toHaveProperty('nba');
    expect(Object.keys(REPORTER_DATABASE.nba).length).toBeGreaterThan(0);
  });

  test('reporter has source_type field', () => {
    const reporter = REPORTER_DATABASE.nba.shams_charania;
    expect(reporter).toHaveProperty('source_type');
    expect(typeof reporter.source_type).toBe('string');
  });
});

describe('Reporter trust escalation', () => {
  test('promote from reliable to verified at 20+ tracked and 90%+ accuracy', () => {
    const result = escalateTrust({ trust: 'reliable', tracked: 25, accuracy: 0.92 });
    expect(result.level).toBe('verified');
  });

  test('stay at reliable if accuracy below 90%', () => {
    const result = escalateTrust({ trust: 'reliable', tracked: 25, accuracy: 0.85 });
    expect(result.level).toBe('reliable');
  });

  test('authoritative requires 30+ tracked and 95%+ accuracy', () => {
    const result = escalateTrust({ trust: 'verified', tracked: 35, accuracy: 0.96 });
    expect(result.level).toBe('authoritative');
  });

  test('badge for authoritative is confirmed', () => {
    const result = escalateTrust({ trust: 'verified', tracked: 35, accuracy: 0.96 });
    expect(result.badge).toBe('confirmed');
  });
});

describe('Tweet parsing', () => {
  test('"will start" detected as confirmed_playing', () => {
    const result = parseTweet('LeBron James will start tonight');
    expect(result.status).toBe('confirmed_playing');
  });

  test('"scratched" detected as scratched', () => {
    const result = parseTweet('Giannis has been scratched from the lineup');
    expect(result.status).toBe('scratched');
  });

  test('"game-time decision" detected as questionable', () => {
    const result = parseTweet('Jaylen Brown is a game-time decision');
    expect(result.status).toBe('questionable');
  });

  test('past tense "was out" filtered', () => {
    const result = parseTweet('Curry was out for the game last week');
    expect(result).toBeNull();
  });

  test('"yesterday" filtered', () => {
    const result = parseTweet('Player was scratched yesterday');
    expect(result).toBeNull();
  });

  test('"last night" filtered', () => {
    const result = parseTweet('He sat out last night');
    expect(result).toBeNull();
  });

  test('returns null for irrelevant text', () => {
    const result = parseTweet('Great weather in Boston today');
    expect(result).toBeNull();
  });

  test('player OUT returns confidence 0.90', () => {
    const result = parseTweet('Player is OUT tonight');
    expect(result.confidence).toBe(0.90);
  });
});

describe('Odds API response parsing', () => {
  test('extracts player_name from outcome', () => {
    const market = { outcomes: [{ name: 'LeBron James', point: 25.5 }] };
    const result = parseOddsOutcome(market, 'draftkings');
    expect(result.player_name).toBe('LeBron James');
  });

  test('extracts line from point', () => {
    const market = { outcomes: [{ name: 'LeBron James', point: 25.5 }] };
    const result = parseOddsOutcome(market, 'draftkings');
    expect(result.line).toBe(25.5);
  });

  test('extracts bookmaker key', () => {
    const market = { outcomes: [{ name: 'LeBron James', point: 25.5 }] };
    const result = parseOddsOutcome(market, 'fanduel');
    expect(result.bookmaker).toBe('fanduel');
  });

  test('handles missing outcomes gracefully', () => {
    expect(parseOddsOutcome({}, 'draftkings')).toBeNull();
    expect(parseOddsOutcome({ outcomes: [] }, 'draftkings')).toBeNull();
    expect(parseOddsOutcome(null, 'draftkings')).toBeNull();
  });
});

describe('Line movement detection', () => {
  test('flags movement >= 0.5', () => {
    const result = detectLineMovement(24.5, 25.5);
    expect(result.flagged).toBe(true);
    expect(result.movement).toBe(1.0);
  });

  test('ignores movement < 0.5', () => {
    const result = detectLineMovement(24.5, 24.8);
    expect(result).toBeNull();
  });

  test('direction is up when current > opening', () => {
    const result = detectLineMovement(24.5, 25.5);
    expect(result.direction).toBe('up');
  });
});

describe('Weather dome detection', () => {
  test('dome parks return temperature=72', () => {
    const weather = getWeatherForPark('tropicana_field', {});
    expect(weather.temperature).toBe(72);
  });

  test('dome parks return wind=0', () => {
    const weather = getWeatherForPark('chase_field', {});
    expect(weather.wind).toBe(0);
  });

  test('ball_carry_factor for dome is 1.0', () => {
    const weather = getWeatherForPark('minute_maid', {});
    expect(weather.ball_carry_factor).toBe(1.0);
  });
});

describe('Weather ball carry', () => {
  test('hot weather increases carry (>72F)', () => {
    const factor = ballCarryFactor(90, 50);
    expect(factor).toBeGreaterThan(1.0);
  });

  test('humid weather decreases carry (>50%)', () => {
    const factor = ballCarryFactor(72, 80);
    expect(factor).toBeLessThan(1.0);
  });

  test('neutral at 72F/50%', () => {
    const factor = ballCarryFactor(72, 50);
    expect(factor).toBe(1.0);
  });
});

describe('Catcher framing', () => {
  test('framing value clamped at upper bound 0.5', () => {
    expect(clampFramingValue(0.8)).toBe(0.5);
  });

  test('framing value clamped at lower bound -0.5', () => {
    expect(clampFramingValue(-0.9)).toBe(-0.5);
  });
});

describe('Umpire / referee minimums', () => {
  test('umpire returns 0.0 below 30 games', () => {
    expect(getUmpireAdjustment({ games: 15, k_rate_delta: 0.12 })).toBe(0.0);
  });

  test('referee returns 0.0 below 30 games', () => {
    expect(getRefereeAdjustment({ games: 20, foul_rate_delta: 0.08 })).toBe(0.0);
  });

  test('umpire returns adjustment at 30+ games', () => {
    const adj = getUmpireAdjustment({ games: 45, k_rate_delta: 0.12 });
    expect(adj).toBe(0.12);
  });

  test('referee returns adjustment at 30+ games', () => {
    const adj = getRefereeAdjustment({ games: 30, foul_rate_delta: 0.08 });
    expect(adj).toBe(0.08);
  });
});

describe('MLB lineup parsing', () => {
  test('lineup has batting_order field', () => {
    const entry = parseLineupEntry({ player: 'Mookie Betts', batting_order: 1, position: 'RF', source: 'official_api' });
    expect(entry).toHaveProperty('batting_order');
    expect(entry.batting_order).toBe(1);
  });

  test('lineup has position field', () => {
    const entry = parseLineupEntry({ player: 'Freddie Freeman', batting_order: 3, position: '1B', source: 'official_api' });
    expect(entry).toHaveProperty('position');
    expect(entry.position).toBe('1B');
  });

  test('status is confirmed from official API', () => {
    const entry = parseLineupEntry({ player: 'Shohei Ohtani', batting_order: 2, position: 'DH', source: 'official_api' });
    expect(entry.status).toBe('confirmed');
  });
});

describe('ABS challenge system', () => {
  test('discipline_score from chase_rate + bb_rate is 0-1', () => {
    const score = disciplineScore(0.3, 0.12);
    expect(score).toBeGreaterThanOrEqual(0);
    expect(score).toBeLessThanOrEqual(1);
  });

  test('elite discipline (>0.7) gets -5% K adjustment', () => {
    const adj = absKAdjustment(0.8);
    expect(adj).toBe(-0.05);
  });

  test('low discipline gets no benefit', () => {
    const adj = absKAdjustment(0.4);
    expect(adj).toBe(0);
  });

  test('framing 50% effective vs disciplined batter', () => {
    const full = framingVsDisciplined(0.3, 0.5);
    const reduced = framingVsDisciplined(0.3, 0.8);
    expect(full).toBe(0.3);
    expect(reduced).toBe(0.15);
  });
});
