const fs = require('fs');
const path = require('path');

const mockRefAssignments = { current: null };
const mockRefProfiles = { current: [] };
const mockCoachProfile = { current: null };

jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({
    from(table) {
      const proxy = {
        select() { return proxy; },
        eq() { return proxy; },
        in() { return Promise.resolve({ data: mockRefProfiles.current, error: null }); },
        maybeSingle() {
          if (table === 'game_ref_assignments') return Promise.resolve({ data: mockRefAssignments.current, error: null });
          if (table === 'coach_profiles') return Promise.resolve({ data: mockCoachProfile.current, error: null });
          return Promise.resolve({ data: null, error: null });
        },
        upsert() { return Promise.resolve({ error: null }); },
      };
      return proxy;
    },
  }),
}));

const refSignals = require('../../src/services/intelligence/refSignals');
const coachSignals = require('../../src/services/intelligence/coachSignals');
const lineupSignals = require('../../src/services/intelligence/lineupSignals');

beforeEach(() => {
  mockRefAssignments.current = null;
  mockRefProfiles.current = [];
  mockCoachProfile.current = null;
});

describe('refSignals', () => {
  test('getRefImpact returns null when no assignment', async () => {
    expect(await refSignals.getRefImpact('g-nope')).toBeNull();
  });

  test('uses precomputed crew values if present on assignment row', async () => {
    mockRefAssignments.current = {
      ref1_name: 'Scott Foster',
      ref2_name: 'Tony Brothers',
      ref3_name: null,
      ref_crew_avg_fouls: 22.5,
      ref_crew_pace_impact: -0.3,
    };
    const r = await refSignals.getRefImpact('g-fast');
    expect(r.avg_fouls).toBe(22.5);
    expect(r.pace_impact).toBe(-0.3);
    expect(r.crew).toHaveLength(2);
  });

  test('averages ref_profiles when assignment has no precomputed values', async () => {
    mockRefAssignments.current = {
      ref1_name: 'Foster', ref2_name: 'Brothers', ref3_name: 'Goble',
      ref_crew_avg_fouls: null, ref_crew_pace_impact: null,
    };
    mockRefProfiles.current = [
      { ref_name: 'Foster', avg_fouls_per_game: 22, avg_free_throws_per_game: 24, pace_impact: -0.5, home_whistle_bias: 0.1 },
      { ref_name: 'Brothers', avg_fouls_per_game: 20, avg_free_throws_per_game: 22, pace_impact: 0.2, home_whistle_bias: 0.0 },
    ];
    const r = await refSignals.getRefImpact('g-avg');
    expect(r.avg_fouls).toBe(21);
    expect(r.pace_impact).toBeCloseTo(-0.15, 5);
  });
});

describe('coachSignals', () => {
  test('returns null when coach not in DB nor seed', async () => {
    expect(await coachSignals.getCoachImpact('nba', 'GHOST')).toBeNull();
  });

  test('falls back to seed when DB has no record (NYK is in seed)', async () => {
    const impact = await coachSignals.getCoachImpact('nba', 'NYK');
    expect(impact).not.toBeNull();
    expect(impact.coach_name).toBe('Tom Thibodeau');
    expect(impact.pace_delta).toBeCloseTo(96.5 - 98.1, 5);
    expect(impact.adjusted_pace_delta).toBe(impact.pace_delta * impact.tenure_adjustment);
  });

  test('returns system_override when primary player is out', async () => {
    const impact = await coachSignals.getCoachImpact('nba', 'NYK', { primary_player_status: 'out' });
    expect(impact.system_override).toBe('motion');
    expect(impact.without_primary_pace_shift).toBe(1.5);
  });

  test('tenureAdjustment maps tenure to 0..1 linearly capped', () => {
    expect(coachSignals.tenureAdjustment(0)).toBe(0);
    expect(coachSignals.tenureAdjustment(20)).toBeCloseTo(0.5, 5);
    expect(coachSignals.tenureAdjustment(40)).toBe(1);
    expect(coachSignals.tenureAdjustment(400)).toBe(1);
  });
});

describe('lineupSignals', () => {
  test('classifyByUsage thresholds', () => {
    expect(lineupSignals.classifyByUsage(0.32)).toBe('primary_handler');
    expect(lineupSignals.classifyByUsage(0.22)).toBe('secondary');
    expect(lineupSignals.classifyByUsage(0.12)).toBe('role_player');
    expect(lineupSignals.classifyByUsage(undefined)).toBe('role_player');
  });

  test('roleValue maps role labels to 1.0/0.5/0.0', () => {
    expect(lineupSignals.roleValue('primary_handler')).toBe(1.0);
    expect(lineupSignals.roleValue('secondary')).toBe(0.5);
    expect(lineupSignals.roleValue('role_player')).toBe(0.0);
  });

  test('getProjectedStarters walks the box score', async () => {
    // Use the fixture from 6a — real ESPN box.
    const fixture = JSON.parse(fs.readFileSync(
      path.join(__dirname, '..', 'fixtures', 'nba-boxscore-sample.json'),
      'utf8',
    ));
    const out = await lineupSignals.getProjectedStarters('nba', 'g', fixture);
    expect(out.home.length).toBeGreaterThan(0);
    expect(out.away.length).toBeGreaterThan(0);
    expect(out.home[0].role).toBe('primary_handler');
  });
});

describe('coaches.json seed', () => {
  test('seed file is valid JSON with the expected shape', () => {
    const seed = coachSignals.loadSeed();
    expect(seed.coaches).toBeDefined();
    expect(Array.isArray(seed.coaches)).toBe(true);
    expect(seed.coaches[0]).toHaveProperty('coach_name');
    expect(seed.coaches[0]).toHaveProperty('team');
  });
});
