#!/usr/bin/env node
/**
 * HEAL DRY-RUN (Order 2, Phase 0) — REPORT ONLY. WRITES NOTHING.
 *
 * Classifies every ledger row's stored game_date against the real schedule and
 * re-derives what opponent a grade would have been bound to, so the heal is
 * planned against verified ground truth instead of the "by hour" proxy.
 *
 * KEY QUESTION IT ANSWERS: baseball is played in SERIES. A one-day-off opponent
 * bind may land on the SAME opponent, in which case the grade was never harmed.
 * Only a bind that changes the opponent is real damage.
 */

const sched = require('../src/services/scheduleService');

const norm = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '');

function teamsFromGameId(gameId) {
  // mlb:2026-07-16:NewYorkMets@PhiladelphiaPhillies  (may carry "(Game1)")
  const m = String(gameId || '').match(/^([a-z]+):(\d{4}-\d{2}-\d{2}):(.+)@(.+)$/);
  if (!m) return null;
  return { sport: m[1], date: m[2], away: m[3].replace(/\(Game\d+\)/i, ''), home: m[4].replace(/\(Game\d+\)/i, '') };
}

function addDays(d, n) {
  const t = new Date(`${d}T12:00:00Z`);
  t.setUTCDate(t.getUTCDate() + n);
  return t.toISOString().slice(0, 10);
}

const schedCache = new Map();
async function scheduleFor(sport, date) {
  const k = `${sport}:${date}`;
  if (!schedCache.has(k)) {
    try { schedCache.set(k, (await sched.getSchedule(sport, date)) || []); }
    catch { schedCache.set(k, []); }
  }
  return schedCache.get(k);
}

const abbrOf = (t) => norm(t && (t.abbreviation || t.name || t.displayName));
const nameOf = (t) => String((t && (t.displayName || t.name || t.abbreviation)) || '');

/** Find the game a team played on a date. Returns {opponent, isHome, id, count}. */
async function gameForTeam(sport, date, teamName) {
  const games = await scheduleFor(sport, date);
  const want = norm(teamName);
  const hits = [];
  for (const g of games) {
    const h = abbrOf(g.homeTeam);
    const a = abbrOf(g.awayTeam);
    const match = (x) => !!x && !!want && (x === want || x.includes(want) || want.includes(x));
    if (match(h)) hits.push({ opponent: nameOf(g.awayTeam), isHome: true, id: g.id });
    else if (match(a)) hits.push({ opponent: nameOf(g.homeTeam), isHome: false, id: g.id });
  }
  return hits.length ? { ...hits[0], count: hits.length } : null;
}

(async () => {
  // Supabase REST is unreachable from this dev box (same restriction as :5432),
  // so rows are exported via the MCP SQL path and read from a local JSON file.
  const rows = JSON.parse(require('fs').readFileSync(process.argv[2], 'utf8'));
  console.log(`rows fetched: ${rows.length}\n`);

  const dateClass = { CORRECT: 0, MISDATED: 0, UNBINDABLE: 0 };
  const oppClass = { MATCH: 0, MISMATCH: 0, UNDETERMINED: 0, NOT_AFFECTED: 0 };
  const misdatedEx = [];
  const mismatchEx = [];
  const dh = [];
  const unbindableBy = {};
  const mismatchBy = {};
  const dhRows = new Set();
  const mismatchIds = [];

  for (const r of rows) {
    const t = teamsFromGameId(r.game_id);
    const hourUtc = Number(r.hour);
    const affected = hourUtc <= 6;              // window proven live (00–06 UTC)

    if (!t) { dateClass.UNBINDABLE += 1; oppClass.UNDETERMINED += 1;
      const k=`${r.sport} ${r.game_date} (no game_id parse)`; unbindableBy[k]=(unbindableBy[k]||0)+1; continue; }

    // --- date axis: did these teams actually play on the stored date? ---
    const onStored = await gameForTeam(r.sport, r.game_date, t.home);
    if (onStored) {
      dateClass.CORRECT += 1;
      if (onStored.count > 1) { dh.push({ id: r.id, date: r.game_date, team: t.home, games: onStored.count }); dhRows.add(r.id); }
    } else {
      const next = await gameForTeam(r.sport, addDays(r.game_date, 1), t.home);
      dateClass[next ? 'MISDATED' : 'UNBINDABLE'] += 1;
      if (!next) { const k=`${r.sport} ${r.game_date}`; unbindableBy[k]=(unbindableBy[k]||0)+1; }
      if (next && misdatedEx.length < 6) {
        misdatedEx.push({ player: r.player_name, stored: r.game_date, real: addDays(r.game_date, 1), outcome: r.outcome });
      }
    }

    // --- grade axis: would the wrong-day bind have changed the OPPONENT? ---
    if (!affected) { oppClass.NOT_AFFECTED += 1; continue; }
    const trueDate = onStored ? r.game_date : addDays(r.game_date, 1);
    const trueGame = onStored || await gameForTeam(r.sport, trueDate, t.home);
    const boundGame = await gameForTeam(r.sport, addDays(trueDate, -1), t.home); // ESPN "yesterday"
    if (!trueGame) { oppClass.UNDETERMINED += 1; continue; }
    if (!boundGame) { oppClass.MATCH += 1; continue; }   // no prior game → nothing wrong to bind
    if (norm(trueGame.opponent) === norm(boundGame.opponent) && trueGame.isHome === boundGame.isHome) {
      oppClass.MATCH += 1;                                // SERIES: same opponent, no harm
    } else {
      oppClass.MISMATCH += 1;
      const k=`${r.sport} ${trueDate} settled=${r.outcome||'PENDING'}`; mismatchBy[k]=(mismatchBy[k]||0)+1;
      mismatchIds.push({ id: r.id, sport: r.sport, date: trueDate, outcome: r.outcome,
        graded_vs: boundGame.opponent, true_opponent: trueGame.opponent });
      if (mismatchEx.length < 8) {
        mismatchEx.push({
          player: r.player_name, date: trueDate,
          graded_vs: `${boundGame.opponent}${boundGame.isHome ? ' (H)' : ' (A)'}`,
          true_opponent: `${trueGame.opponent}${trueGame.isHome ? ' (H)' : ' (A)'}`,
        });
      }
    }
  }

  console.log('=== 0.1 DATE AXIS ===');
  console.log(JSON.stringify(dateClass, null, 2));
  if (misdatedEx.length) { console.log('mis-dated examples:'); misdatedEx.forEach((e) => console.log('  ', JSON.stringify(e))); }

  console.log('\n=== 0.4 GRADE AXIS (affected window 00–06 UTC) ===');
  console.log(JSON.stringify(oppClass, null, 2));
  if (mismatchEx.length) { console.log('opponent MISMATCH examples:'); mismatchEx.forEach((e) => console.log('  ', JSON.stringify(e))); }

  console.log('\n=== UNBINDABLE breakdown (sport date -> rows) ===');
  Object.entries(unbindableBy).sort().forEach(([k,v])=>console.log(`   ${k}: ${v}`));
  console.log('\n=== MISMATCH breakdown (sport trueDate settled? -> rows) ===');
  Object.entries(mismatchBy).sort().forEach(([k,v])=>console.log(`   ${k}: ${v}`));
  console.log('\n=== 0.6 DOUBLEHEADER: rows on a date where the team played 2 games ===');
  console.log(`   affected rows: ${dhRows.size}`);
  const byTeam={}; dh.forEach(d=>{const k=`${d.date} ${d.team}`; byTeam[k]=(byTeam[k]||0)+1;});
  Object.entries(byTeam).sort().forEach(([k,v])=>console.log(`   ${k}: ${v} rows`));

  require('fs').writeFileSync('/tmp/claude-1000/-home-kev-mastermind-vyndr/a6b79396-5b11-477d-8353-0672bd5789c1/scratchpad/mismatch_ids.json', JSON.stringify(mismatchIds, null, 1));
  require('fs').writeFileSync('/tmp/claude-1000/-home-kev-mastermind-vyndr/a6b79396-5b11-477d-8353-0672bd5789c1/scratchpad/dh_ids.json', JSON.stringify([...dhRows], null, 1));
  console.log(`\nID files written: mismatch=${mismatchIds.length} doubleheader=${dhRows.size}`);
  await new Promise((r) => process.stdout.write('', r));
  process.exit(0);
})().catch((e) => { console.error('dry-run failed:', e.message); process.exit(1); });
