import { describe, it, expect } from 'vitest';
import {
  letterFor,
  percentageFromMarks,
  percentageLetter,
  resolveScale,
  scaleOptions,
  isValidScaleKey,
  SCALES,
  DEFAULT_SCALE_KEY,
} from './gradeScales';

describe('gradeScales', () => {
  describe('letterFor (O-Level, default)', () => {
    it('maps O-Level bands correctly: A* at 80+, A at 70-79, ... U below 30', () => {
      expect(letterFor(85).letter).toBe('1');
      expect(letterFor(85).descriptor).toBe('A*');
      expect(letterFor(75).letter).toBe('2');
      expect(letterFor(75).descriptor).toBe('A');
      expect(letterFor(67).letter).toBe('3');
      expect(letterFor(67).descriptor).toBe('B');
      expect(letterFor(62).letter).toBe('4');
      expect(letterFor(62).descriptor).toBe('C');
      expect(letterFor(20).letter).toBe('9');
      expect(letterFor(20).descriptor).toBe('U');
    });

    it('uses Math.floor so 79.5 stays in band 2 (A) not band 1 (A*)', () => {
      expect(letterFor(79.5).letter).toBe('2');
      expect(letterFor(79.9).letter).toBe('2');
    });

    it('clamps out-of-range inputs to the scale boundary', () => {
      // 200% is impossible but the resolver must not crash; it lands in band 1
      expect(letterFor(200).letter).toBe('1');
      // -5% is impossible; should land in the lowest band (U for olevel)
      expect(letterFor(-5).letter).toBe('9');
    });

    it('respects an explicit scaleKey for Cambridge IGCSE', () => {
      const r = letterFor(85, 'cambridge_igcse');
      expect(r.scale).toBe('cambridge_igcse');
      expect(r.letter).toBe('A'); // 80-89 in IGCSE
    });

    it('falls back to DEFAULT_SCALE_KEY for unknown scale keys', () => {
      const r = letterFor(75, 'nonexistent-scale');
      expect(r.scale).toBe(DEFAULT_SCALE_KEY);
      expect(r.letter).toBe('2');
    });
  });

  describe('percentageFromMarks', () => {
    it('returns 0 for total <= 0 (defensive)', () => {
      expect(percentageFromMarks(50, 0)).toBe(0);
      expect(percentageFromMarks(50, -1)).toBe(0);
    });
    it('rounds to 2 decimals', () => {
      expect(percentageFromMarks(75, 100)).toBe(75);
      expect(percentageFromMarks(1, 3)).toBe(33.33);
    });
  });

  describe('resolveScale', () => {
    it('uses rowScale when valid', () => {
      expect(resolveScale({ rowScale: 'cambridge_alevel' }).key).toBe('cambridge_alevel');
    });
    it('falls back through parentScale, defaultScale, then DEFAULT_SCALE_KEY', () => {
      expect(resolveScale({ rowScale: 'invalid', parentScale: 'zimsec_primary' }).key).toBe('zimsec_primary');
      expect(resolveScale({ rowScale: 'invalid', parentScale: 'invalid', defaultScale: 'zimsec_alevel' }).key).toBe('zimsec_alevel');
      expect(resolveScale({}).key).toBe(DEFAULT_SCALE_KEY);
    });
  });

  describe('scaleOptions / isValidScaleKey', () => {
    it('returns one option per scale', () => {
      const opts = scaleOptions();
      expect(opts.length).toBe(Object.keys(SCALES).length);
    });
    it('validates known scale keys', () => {
      expect(isValidScaleKey('zimsec_olevel')).toBe(true);
      expect(isValidScaleKey('not-a-scale')).toBe(false);
      expect(isValidScaleKey(42)).toBe(false);
      expect(isValidScaleKey(null)).toBe(false);
    });
  });

  describe('percentageLetter formatter', () => {
    it('formats with descriptor when present (O-Level)', () => {
      expect(percentageLetter(75, 'zimsec_olevel')).toBe('75% (2 / A)');
    });
    it('formats without descriptor for primary/alevel (letter == descriptor)', () => {
      // For zimsec_primary, letter is just a number; no descriptor
      expect(percentageLetter(75, 'zimsec_primary')).toBe('75% (2)');
    });
    it('returns em-dash for null/NaN', () => {
      expect(percentageLetter(null)).toBe('—');
      expect(percentageLetter(NaN)).toBe('—');
    });
  });
});
