const mockState = { rows: new Map(), upserts: [] };

function mockKeyOf(s, g, p) { return `${s}|${g}|${p}`; }

jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({
    from() {
      const ctx = { filters: {} };
      const proxy = {
        select() { return proxy; },
        eq(col, val) { ctx.filters[col] = val; return proxy; },
        maybeSingle() {
          const row = mockState.rows.get(mockKeyOf(ctx.filters.sport, ctx.filters.grade, ctx.filters.period));
          return Promise.resolve({ data: row || null, error: null });
        },
        upsert(row) {
          mockState.upserts.push(row);
          mockState.rows.set(mockKeyOf(row.sport, row.grade, row.period), { ...row });
          return Promise.resolve({ error: null });
        },
        then(resolve) {
          // For getAccuracyDashboard — list all rows with period filter.
          const period = ctx.filters.period;
          const all = Array.from(mockState.rows.values()).filter((r) => !period || r.period === period);
          return resolve({ data: all, error: null });
        },
      };
      return proxy;
    },
  }),
}));

const at = require('../../src/services/intelligence/accuracyTracker');

beforeEach(() => {
  mockState.rows.clear();
  mockState.upserts.length = 0;
});

describe('accuracyTracker.recordResolution', () => {
  test('increments hit + total_graded across all three periods', async () => {
    await at.recordResolution('nba', 'A', 'hit');
    expect(mockState.upserts).toHaveLength(3); // all_time + 30d + 7d
    for (const u of mockState.upserts) {
      expect(u.total_graded).toBe(1);
      expect(u.total_hit).toBe(1);
      expect(u.total_miss).toBe(0);
    }
  });

  test('push/void don\'t count toward hit rate', async () => {
    await at.recordResolution('nba', 'A', 'push');
    await at.recordResolution('nba', 'A', 'void');
    const row = mockState.rows.get('nba|A|all_time');
    expect(row.total_hit).toBe(0);
    expect(row.total_miss).toBe(0);
    expect(row.total_push).toBe(1);
    expect(row.total_void).toBe(1);
    expect(row.hit_rate).toBeNull();
  });

  test('hit_rate is hits/(hits+misses) only', async () => {
    for (let i = 0; i < 7; i += 1) await at.recordResolution('nba', 'A', 'hit');
    for (let i = 0; i < 3; i += 1) await at.recordResolution('nba', 'A', 'miss');
    const row = mockState.rows.get('nba|A|all_time');
    expect(row.hit_rate).toBeCloseTo(0.7, 5);
  });
});

describe('accuracyTracker — baseline lock', () => {
  test('baseline locks at 100 decisive resolutions', async () => {
    for (let i = 0; i < 99; i += 1) await at.recordResolution('nba', 'A', 'hit');
    let row = mockState.rows.get('nba|A|all_time');
    expect(row.baseline_locked).toBe(false);
    await at.recordResolution('nba', 'A', 'hit');
    row = mockState.rows.get('nba|A|all_time');
    expect(row.baseline_locked).toBe(true);
    expect(row.baseline_hit_rate).toBe(1.0);
  });

  test('only locks the all_time period', async () => {
    for (let i = 0; i < 100; i += 1) await at.recordResolution('nba', 'A', 'hit');
    expect(mockState.rows.get('nba|A|all_time').baseline_locked).toBe(true);
    expect(mockState.rows.get('nba|A|last_30d').baseline_locked).toBe(false);
  });

  test('pushes do not contribute to baseline trigger', async () => {
    for (let i = 0; i < 99; i += 1) await at.recordResolution('nba', 'A', 'hit');
    await at.recordResolution('nba', 'A', 'push');
    expect(mockState.rows.get('nba|A|all_time').baseline_locked).toBe(false);
  });
});

describe('accuracyTracker.getAccuracy', () => {
  test('reports expected hit rate by grade', async () => {
    const r = await at.getAccuracy('nba', 'A+', 'all_time');
    expect(r.expected).toBe(0.65);
  });

  test('returns deltas relative to baseline once locked', async () => {
    for (let i = 0; i < 80; i += 1) await at.recordResolution('nba', 'A', 'hit');
    for (let i = 0; i < 20; i += 1) await at.recordResolution('nba', 'A', 'miss');
    const r = await at.getAccuracy('nba', 'A', 'all_time');
    expect(r.locked).toBe(true);
    expect(r.baseline).toBeCloseTo(0.8, 5);
    expect(r.delta).toBe(0);
  });
});

describe('accuracyTracker.getAllAccuracy', () => {
  test('returns one entry per known grade tier', async () => {
    const all = await at.getAllAccuracy('nba');
    const tiers = all.map((r) => r.grade);
    expect(tiers).toContain('A+');
    expect(tiers).toContain('F');
    expect(all.length).toBe(Object.keys(at.EXPECTED_HIT_RATES).length);
  });
});
