const {
  ROLE_TAXONOMY,
  calculateRoleVariance,
  getDominantRole,
  detectRoleElevation,
  getConditionalProfile,
  calculateRoleProfile,
} = require('../../src/services/roleProfileEngine');

const {
  calculateStability,
  applyDecayWeights,
} = require('../../src/services/roleStabilityEngine');

describe('roleProfileEngine', () => {
  // ─── calculateRoleVariance ──────────────────────────────────

  test('returns 0 for a single-role profile', () => {
    const profile = { PRIMARY_BALL_HANDLER: 1.0 };
    expect(calculateRoleVariance(profile)).toBe(0);
  });

  test('returns ~1.0 for perfectly equal distribution', () => {
    const profile = {};
    for (const role of ROLE_TAXONOMY) {
      profile[role] = 1 / ROLE_TAXONOMY.length;
    }
    const variance = calculateRoleVariance(profile);
    expect(variance).toBeGreaterThan(0.99);
    expect(variance).toBeLessThanOrEqual(1.0);
  });

  test('returns value strictly between 0 and 1 for mixed profile', () => {
    const profile = {
      PRIMARY_BALL_HANDLER: 0.50,
      SECONDARY_PLAYMAKER: 0.30,
      CONNECTOR: 0.20,
    };
    const variance = calculateRoleVariance(profile);
    expect(variance).toBeGreaterThan(0);
    expect(variance).toBeLessThan(1);
  });

  test('handles empty profile gracefully', () => {
    expect(calculateRoleVariance({})).toBe(0);
  });

  test('ignores zero-weight roles in entropy calculation', () => {
    const profileA = { PRIMARY_BALL_HANDLER: 0.5, CONNECTOR: 0.5 };
    const profileB = {
      PRIMARY_BALL_HANDLER: 0.5,
      CONNECTOR: 0.5,
      FLOOR_RAISER: 0,
      PAINT_PRESENCE: 0,
    };
    // Both should give same result since zeros are filtered
    expect(calculateRoleVariance(profileA)).toBeCloseTo(calculateRoleVariance(profileB), 5);
  });

  // ─── getDominantRole ────────────────────────────────────────

  test('returns the role with the highest weight', () => {
    const profile = {
      PRIMARY_BALL_HANDLER: 0.15,
      FLOOR_RAISER: 0.55,
      CONNECTOR: 0.30,
    };
    expect(getDominantRole(profile)).toBe('FLOOR_RAISER');
  });

  test('returns null for empty profile', () => {
    expect(getDominantRole({})).toBeNull();
  });

  // ─── detectRoleElevation ────────────────────────────────────

  test('detects elevation when delta exceeds threshold', () => {
    const base = {
      PRIMARY_BALL_HANDLER: 0.20,
      SECONDARY_PLAYMAKER: 0.50,
      CONNECTOR: 0.30,
    };
    const tonight = {
      PRIMARY_BALL_HANDLER: 0.60,
      SECONDARY_PLAYMAKER: 0.25,
      CONNECTOR: 0.15,
    };
    const result = detectRoleElevation(base, tonight, 0.20);
    expect(result.elevated).toBe(true);
    expect(result.elevatedRole).toBe('PRIMARY_BALL_HANDLER');
    expect(result.delta).toBeGreaterThan(0.20);
  });

  test('does not flag elevation when delta is below threshold', () => {
    const base = {
      PRIMARY_BALL_HANDLER: 0.40,
      SECONDARY_PLAYMAKER: 0.35,
      CONNECTOR: 0.25,
    };
    const tonight = {
      PRIMARY_BALL_HANDLER: 0.45,
      SECONDARY_PLAYMAKER: 0.30,
      CONNECTOR: 0.25,
    };
    const result = detectRoleElevation(base, tonight, 0.20);
    expect(result.elevated).toBe(false);
    expect(result.elevatedRole).toBeNull();
  });

  // ─── getConditionalProfile ──────────────────────────────────

  test('returns conditional profile for valid condition', () => {
    const conditionals = {
      star_out: { PRIMARY_BALL_HANDLER: 0.70, FLOOR_RAISER: 0.30 },
      closing_lineup: { SWITCHABLE_DEFENDER: 0.60, CONNECTOR: 0.40 },
    };
    const result = getConditionalProfile(conditionals, 'star_out');
    expect(result).toEqual({ PRIMARY_BALL_HANDLER: 0.70, FLOOR_RAISER: 0.30 });
  });

  test('returns null for invalid condition key', () => {
    const conditionals = { star_out: { PRIMARY_BALL_HANDLER: 1.0 } };
    expect(getConditionalProfile(conditionals, 'garbage_condition')).toBeNull();
  });

  // ─── calculateRoleProfile ──────────────────────────────────

  test('produces distribution that sums to approximately 1.0', () => {
    const gameLogStats = [
      {
        usage_rate: 28,
        assist_rate: 32,
        three_point_share: 40,
        off_ball_movement: 30,
        paint_touches: 4,
        rebounds_per_game: 5,
        defensive_versatility: 45,
        screen_assists: 2,
      },
      {
        usage_rate: 30,
        assist_rate: 35,
        three_point_share: 38,
        off_ball_movement: 25,
        paint_touches: 3,
        rebounds_per_game: 4.5,
        defensive_versatility: 50,
        screen_assists: 3,
      },
    ];
    const profile = calculateRoleProfile(gameLogStats);
    const total = Object.values(profile).reduce((s, v) => s + v, 0);
    expect(total).toBeGreaterThan(0.95);
    expect(total).toBeLessThanOrEqual(1.05);
  });

  test('returns empty object for empty game logs', () => {
    expect(calculateRoleProfile([])).toEqual({});
  });
});

describe('roleStabilityEngine', () => {
  // ─── calculateStability ─────────────────────────────────────

  test('low variance player gets no decay (all weights 1.0)', () => {
    const profile = { PRIMARY_BALL_HANDLER: 0.85, CONNECTOR: 0.15 };
    const varianceScore = 0.15; // below 0.2 threshold
    const history = [
      { date: '2026-01-01', roleProfile: { PRIMARY_BALL_HANDLER: 0.85, CONNECTOR: 0.15 } },
      { date: '2026-01-05', roleProfile: { PRIMARY_BALL_HANDLER: 0.80, CONNECTOR: 0.20 } },
      { date: '2026-01-10', roleProfile: { PRIMARY_BALL_HANDLER: 0.85, CONNECTOR: 0.15 } },
    ];
    const result = calculateStability(profile, varianceScore, history);
    // All decay weights should be 1.0
    expect(result.decay_weights_by_period.every((w) => w === 1.0)).toBe(true);
    expect(result.stability_score).toBeGreaterThan(0.7);
  });

  test('high variance player gets recency decay', () => {
    const profile = {
      PRIMARY_BALL_HANDLER: 0.30,
      FLOOR_RAISER: 0.30,
      CONNECTOR: 0.20,
      SWITCHABLE_DEFENDER: 0.20,
    };
    const varianceScore = 0.65; // above 0.5 threshold
    const history = [
      { date: '2026-01-01', roleProfile: { FLOOR_RAISER: 0.60, CONNECTOR: 0.40 } },
      { date: '2026-01-05', roleProfile: { PRIMARY_BALL_HANDLER: 0.50, CONNECTOR: 0.50 } },
      { date: '2026-01-10', roleProfile: { SWITCHABLE_DEFENDER: 0.55, FLOOR_RAISER: 0.45 } },
      { date: '2026-01-15', roleProfile: { PRIMARY_BALL_HANDLER: 0.30, FLOOR_RAISER: 0.30, CONNECTOR: 0.20, SWITCHABLE_DEFENDER: 0.20 } },
    ];
    const result = calculateStability(profile, varianceScore, history);
    // Older entries should have lower weights than newer
    const weights = result.decay_weights_by_period;
    expect(weights[weights.length - 1]).toBeGreaterThan(weights[0]);
    // Should detect role changes
    expect(result.role_change_events).toBeGreaterThan(0);
  });

  test('stability score is between 0 and 1', () => {
    const profile = { PAINT_PRESENCE: 0.70, SWITCHABLE_DEFENDER: 0.30 };
    const history = [
      { date: '2026-01-01', roleProfile: { PAINT_PRESENCE: 0.65, SWITCHABLE_DEFENDER: 0.35 } },
      { date: '2026-01-10', roleProfile: { PAINT_PRESENCE: 0.70, SWITCHABLE_DEFENDER: 0.30 } },
    ];
    const result = calculateStability(profile, 0.35, history);
    expect(result.stability_score).toBeGreaterThanOrEqual(0);
    expect(result.stability_score).toBeLessThanOrEqual(1);
  });

  // ─── applyDecayWeights ──────────────────────────────────────

  test('all weights are 1.0 when variance is below 0.2', () => {
    const instances = [{}, {}, {}, {}, {}];
    const weights = applyDecayWeights(instances, 0.10);
    expect(weights).toEqual([1.0, 1.0, 1.0, 1.0, 1.0]);
  });

  test('returns empty array for empty instances', () => {
    expect(applyDecayWeights([], 0.6)).toEqual([]);
  });
});
