const {
  SCHEME_TYPES,
  MIN_POSSESSIONS,
  CACHE_TTL,
  getCacheKey,
  extractPnRPossessions,
  classifyScheme,
} = require('../../src/services/schemeClassifier');

describe('Scheme Classifier', () => {

  // --- Constants ---

  test('SCHEME_TYPES contains all 5 classifications', () => {
    expect(SCHEME_TYPES).toEqual(['DROP', 'SWITCH', 'HEDGE', 'MIXED', 'UNKNOWN']);
  });

  test('minimum possessions threshold is 8', () => {
    expect(MIN_POSSESSIONS).toBe(8);
  });

  test('cache TTL is 6 hours (21600 seconds)', () => {
    expect(CACHE_TTL).toBe(21600);
  });

  // --- Cache Key ---

  test('cache key includes opponent and date', () => {
    const key = getCacheKey('BOS', '2026-04-12');
    expect(key).toBe('scheme:BOS:2026-04-12');
  });

  // --- PnR Extraction ---

  test('extractPnRPossessions identifies pick-and-roll plays', () => {
    const plays = [
      { description: 'LeBron pick and roll ball handler' },
      { description: 'AD catches lob pass' },
      { description: 'screen and roll coverage by Smart' },
      { description: 'transition fastbreak layup' },
      { description: 'ball screen switch by Tatum' },
    ];
    const result = extractPnRPossessions(plays);
    expect(result).toHaveLength(3);
  });

  test('extractPnRPossessions handles empty array', () => {
    expect(extractPnRPossessions([])).toEqual([]);
  });

  test('extractPnRPossessions handles null/undefined', () => {
    expect(extractPnRPossessions(null)).toEqual([]);
    expect(extractPnRPossessions(undefined)).toEqual([]);
  });

  test('extractPnRPossessions handles play_description field', () => {
    const plays = [
      { play_description: 'PnR ball handler drive' },
    ];
    const result = extractPnRPossessions(plays);
    expect(result).toHaveLength(1);
  });

  // --- Classification ---

  test('classifyScheme returns UNKNOWN with fewer than 8 possessions', () => {
    const possessions = Array.from({ length: 5 }, () => ({ description: 'drop coverage on screen' }));
    const result = classifyScheme(possessions);
    expect(result.scheme).toBe('UNKNOWN');
    expect(result.reason).toBe('insufficient_data');
    expect(result.possessions_analyzed).toBe(5);
  });

  test('classifyScheme returns DROP when drop coverage dominates', () => {
    const possessions = [
      ...Array.from({ length: 7 }, () => ({ description: 'drop coverage on ball screen' })),
      { description: 'switch on screen action' },
      { description: 'hedge hard show on pick' },
    ];
    const result = classifyScheme(possessions);
    expect(result.scheme).toBe('DROP');
    expect(result.confidence).toBeGreaterThanOrEqual(55);
    expect(result.breakdown).toBeDefined();
    expect(result.breakdown.DROP).toBe(7);
  });

  test('classifyScheme returns SWITCH when switch dominates', () => {
    const possessions = [
      ...Array.from({ length: 8 }, () => ({ description: 'switch on screen swap defenders' })),
      { description: 'drop back on screen' },
      { description: 'drop sag coverage' },
    ];
    const result = classifyScheme(possessions);
    expect(result.scheme).toBe('SWITCH');
    expect(result.confidence).toBeGreaterThanOrEqual(55);
  });

  test('classifyScheme returns HEDGE when hedge/blitz dominates', () => {
    const possessions = [
      ...Array.from({ length: 8 }, () => ({ description: 'hedge hard show blitz screen' })),
      { description: 'switch on ball screen' },
    ];
    const result = classifyScheme(possessions);
    expect(result.scheme).toBe('HEDGE');
  });

  test('classifyScheme returns MIXED when no scheme exceeds 55%', () => {
    const possessions = [
      ...Array.from({ length: 4 }, () => ({ description: 'drop coverage on screen' })),
      ...Array.from({ length: 3 }, () => ({ description: 'switch on screen' })),
      ...Array.from({ length: 3 }, () => ({ description: 'hedge trap on ball screen' })),
    ];
    const result = classifyScheme(possessions);
    expect(result.scheme).toBe('MIXED');
    expect(result.breakdown).toBeDefined();
  });

  test('classifyScheme returns UNKNOWN when no classifiable actions found', () => {
    const possessions = Array.from({ length: 10 }, () => ({ description: 'generic play action' }));
    const result = classifyScheme(possessions);
    expect(result.scheme).toBe('UNKNOWN');
    expect(result.reason).toBe('no_classifiable_actions');
  });

  test('classifyScheme handles null possessions', () => {
    const result = classifyScheme(null);
    expect(result.scheme).toBe('UNKNOWN');
    expect(result.reason).toBe('insufficient_data');
  });

  test('classifyScheme handles empty possessions', () => {
    const result = classifyScheme([]);
    expect(result.scheme).toBe('UNKNOWN');
    expect(result.reason).toBe('insufficient_data');
  });

  // --- Confidence ---

  test('confidence is a percentage rounded to integer', () => {
    const possessions = Array.from({ length: 10 }, () => ({ description: 'drop coverage sag back' }));
    const result = classifyScheme(possessions);
    expect(Number.isInteger(result.confidence)).toBe(true);
    expect(result.confidence).toBeGreaterThanOrEqual(0);
    expect(result.confidence).toBeLessThanOrEqual(100);
  });

  // --- Breakdown ---

  test('breakdown counts sum correctly', () => {
    const possessions = [
      ...Array.from({ length: 5 }, () => ({ description: 'drop coverage on screen' })),
      ...Array.from({ length: 2 }, () => ({ description: 'switch on ball screen' })),
      ...Array.from({ length: 2 }, () => ({ description: 'hedge trap blitz' })),
    ];
    const result = classifyScheme(possessions);
    const { DROP, SWITCH, HEDGE } = result.breakdown;
    expect(DROP).toBe(5);
    expect(SWITCH).toBe(2);
    expect(HEDGE).toBe(2);
  });

  // --- Graceful Degradation ---

  test('scheme result always contains required fields', () => {
    const result = classifyScheme(null);
    expect(result).toHaveProperty('scheme');
    expect(result).toHaveProperty('confidence');
    expect(result).toHaveProperty('possessions_analyzed');
    expect(SCHEME_TYPES).toContain(result.scheme);
  });
});
