process.env.VYNDR_INTERNAL_KEY = 'corr-test-key';

const mockState = {
  resolutions: [],
  updates: [],
};

jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({
    from(table) {
      const ctx = { table };
      const proxy = {
        select() { return proxy; },
        gte() { return Promise.resolve({ data: mockState.resolutions, error: null }); },
        update(patch) {
          mockState.updates.push({ table, patch });
          return {
            eq() { return Promise.resolve({ error: null }); },
            in() { return Promise.resolve({ error: null }); },
          };
        },
      };
      return proxy;
    },
  }),
}));

jest.mock('../../src/utils/rateLimiter', () => ({
  createLimiter: () => ({ waitForToken: async () => true, snapshot: () => ({}) }),
  createCircuitBreaker: () => ({ call: async (fn) => fn(), snapshot: () => ({}) }),
  API_BUDGETS: {
    espn: { tokensPerInterval: 100, interval: 60_000 },
    mlbStats: { tokensPerInterval: 100, interval: 60_000 },
    oddsPapi: { tokensPerInterval: 100, interval: 60_000 },
    sharpApi: { tokensPerInterval: 100, interval: 60_000 },
    openRouter: { tokensPerInterval: 100, interval: 60_000 },
  },
}));

const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({
  get: (...args) => mockAxiosGet(...args),
  post: jest.fn().mockResolvedValue({ status: 200, data: {} }),
}));

jest.mock('../../src/services/distribution/telegram', () => ({
  configured: () => false,
  postToTelegram: async () => ({ ok: true }),
}));

const express = require('express');
const corrections = require('../../src/routes/corrections');

function makeApp() {
  const app = express();
  app.use(express.json());
  app.use('/api/grading', corrections);
  return app;
}

function call(app, body, headers = {}) {
  return new Promise((resolve) => {
    const http = require('http');
    const server = app.listen(0, '127.0.0.1', () => {
      const port = server.address().port;
      const req = http.request({
        host: '127.0.0.1',
        port,
        path: '/api/grading/correct',
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...headers },
      }, (res) => {
        const chunks = [];
        res.on('data', (c) => chunks.push(c));
        res.on('end', () => {
          server.close();
          const raw = Buffer.concat(chunks).toString('utf8');
          let parsed; try { parsed = JSON.parse(raw); } catch { parsed = raw; }
          resolve({ status: res.statusCode, body: parsed });
        });
      });
      req.write(JSON.stringify(body || {}));
      req.end();
    });
  });
}

beforeEach(() => {
  mockState.resolutions = [];
  mockState.updates = [];
  mockAxiosGet.mockReset();
});

describe('POST /api/grading/correct', () => {
  test('rejects without internal key', async () => {
    const app = makeApp();
    const res = await call(app, { hours: 24 });
    expect(res.status).toBe(401);
  });

  test('no-ops when no resolutions in window', async () => {
    mockState.resolutions = [];
    const app = makeApp();
    const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
    expect(res.status).toBe(200);
    expect(res.body).toMatchObject({ checked: 0, corrected: 0 });
  });

  test('detects a flipped result and updates both tables', async () => {
    // Stored: hit at actual=10 (line 9.5 over). Box now reports actual=9 → miss.
    mockState.resolutions = [{
      id: 'res-1', grade_id: 'gh-1', game_id: 'g-correct', sport: 'nba',
      player_espn_id: '999', player_name: 'Test Player', stat_type: 'points',
      line: 9.5, direction: 'over', actual_value: 10, result: 'hit', margin: 0.5,
    }];
    // Build a minimal basketball box-score with the player's points now at 9.
    mockAxiosGet.mockResolvedValue({
      data: {
        boxscore: {
          players: [
            {
              statistics: [{
                labels: ['MIN','PTS','FG','3PT','FT','REB','AST','TO','STL','BLK'],
                athletes: [{ athlete: { id: 999 }, starter: true, stats: ['33','9','3-7','1-4','2-2','5','4','1','1','0'] }],
              }],
            },
            { statistics: [{ athletes: [] }] },
          ],
        },
      },
    });

    const app = makeApp();
    const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
    expect(res.status).toBe(200);
    expect(res.body.checked).toBe(1);
    expect(res.body.corrected).toBe(1);
    expect(res.body.details[0]).toMatchObject({
      flipped: true,
      old: { actual: 10, result: 'hit' },
      new: { actual: 9, result: 'miss' },
    });
    // Both tables updated (resolution_results + grade_history).
    expect(mockState.updates.some((u) => u.table === 'resolution_results')).toBe(true);
    expect(mockState.updates.some((u) => u.table === 'grade_history')).toBe(true);
  });

  test('value change without result flip is a silent update', async () => {
    // Stored: hit at actual=12 (line 9.5 over). Box now reports actual=11 → still HIT, just lower margin.
    mockState.resolutions = [{
      id: 'res-2', grade_id: 'gh-2', game_id: 'g-silent', sport: 'nba',
      player_espn_id: '111', player_name: 'Silent', stat_type: 'points',
      line: 9.5, direction: 'over', actual_value: 12, result: 'hit', margin: 2.5,
    }];
    mockAxiosGet.mockResolvedValue({
      data: {
        boxscore: {
          players: [
            {
              statistics: [{
                labels: ['MIN','PTS','FG','3PT','FT','REB','AST','TO','STL','BLK'],
                athletes: [{ athlete: { id: 111 }, starter: true, stats: ['30','11','5-9','1-2','0-0','3','2','0','0','0'] }],
              }],
            },
            { statistics: [{ athletes: [] }] },
          ],
        },
      },
    });
    const app = makeApp();
    const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
    expect(res.body.checked).toBe(1);
    expect(res.body.corrected).toBe(0);
    expect(res.body.details[0]).toMatchObject({ flipped: false, new: { actual: 11, result: 'hit' } });
  });

  test('no change at all is a no-op (no updates queued)', async () => {
    mockState.resolutions = [{
      id: 'res-3', grade_id: 'gh-3', game_id: 'g-noop', sport: 'nba',
      player_espn_id: '222', player_name: 'NoChange', stat_type: 'points',
      line: 9.5, direction: 'over', actual_value: 10, result: 'hit', margin: 0.5,
    }];
    mockAxiosGet.mockResolvedValue({
      data: {
        boxscore: {
          players: [
            {
              statistics: [{
                labels: ['MIN','PTS','FG','3PT','FT','REB','AST','TO','STL','BLK'],
                athletes: [{ athlete: { id: 222 }, starter: true, stats: ['30','10','5-9','1-2','0-0','3','2','0','0','0'] }],
              }],
            },
            { statistics: [{ athletes: [] }] },
          ],
        },
      },
    });
    const app = makeApp();
    const res = await call(app, { hours: 24 }, { 'X-VYNDR-Internal-Key': 'corr-test-key' });
    expect(res.body.checked).toBe(1);
    expect(res.body.corrected).toBe(0);
    expect(res.body.details).toHaveLength(0);
    expect(mockState.updates).toHaveLength(0);
  });
});
