/**
 * Utility Functions Tests
 */

import {
  formatPercentage,
  formatProgress,
  calculateTrend,
  isValidPostcode,
  debounce,
  buildOfstedListBadge,
  ofstedLegacyAreas,
  metricKind,
  shortName,
  computeYBounds,
} from '@/lib/utils';

describe('formatPercentage', () => {
  it('formats percentages correctly', () => {
    expect(formatPercentage(75.5)).toBe('75.5%');
    expect(formatPercentage(100)).toBe('100.0%');
    expect(formatPercentage(0)).toBe('0.0%');
  });

  it('handles null values', () => {
    expect(formatPercentage(null)).toBe('N/A');
  });
});

describe('formatProgress', () => {
  it('formats progress scores correctly', () => {
    expect(formatProgress(2.5)).toBe('+2.5');
    expect(formatProgress(-1.3)).toBe('-1.3');
    expect(formatProgress(0)).toBe('0.0');
  });

  it('handles null values', () => {
    expect(formatProgress(null)).toBe('N/A');
  });
});

describe('calculateTrend', () => {
  it('calculates upward trend', () => {
    expect(calculateTrend(75, 70)).toBe('up');
  });

  it('calculates downward trend', () => {
    expect(calculateTrend(70, 75)).toBe('down');
  });

  it('calculates stable trend', () => {
    expect(calculateTrend(75, 75)).toBe('stable');
  });

  it('handles null previous value', () => {
    expect(calculateTrend(75, null)).toBe('stable');
  });

  it('handles null current value', () => {
    expect(calculateTrend(null, 75)).toBe('stable');
  });
});

describe('isValidPostcode', () => {
  it('validates correct UK postcodes', () => {
    expect(isValidPostcode('SW1A 1AA')).toBe(true);
    expect(isValidPostcode('M1 1AE')).toBe(true);
    expect(isValidPostcode('B33 8TH')).toBe(true);
  });

  it('rejects invalid postcodes', () => {
    expect(isValidPostcode('INVALID')).toBe(false);
    expect(isValidPostcode('12345')).toBe(false);
    expect(isValidPostcode('')).toBe(false);
  });
});

describe('debounce', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  it('delays function execution', () => {
    const mockFn = jest.fn();
    const debouncedFn = debounce(mockFn, 300);

    debouncedFn('test');
    expect(mockFn).not.toHaveBeenCalled();

    jest.advanceTimersByTime(300);
    expect(mockFn).toHaveBeenCalledWith('test');
    expect(mockFn).toHaveBeenCalledTimes(1);
  });

  it('cancels previous calls', () => {
    const mockFn = jest.fn();
    const debouncedFn = debounce(mockFn, 300);

    debouncedFn('first');
    jest.advanceTimersByTime(150);
    debouncedFn('second');
    jest.advanceTimersByTime(150);
    debouncedFn('third');
    jest.advanceTimersByTime(300);

    expect(mockFn).toHaveBeenCalledWith('third');
    expect(mockFn).toHaveBeenCalledTimes(1);
  });
});

describe('ofstedLegacyAreas', () => {
  it('returns only real grades (1-4) and drops sentinel/null values', () => {
    const areas = ofstedLegacyAreas({
      quality_of_education: 1,
      behaviour_attitudes: 2,
      personal_development: null,
      leadership_management: 9, // sentinel "not applicable" — must be dropped
      early_years_provision: 9, // secondary school — not applicable
      sixth_form_provision: 2,
    });
    expect(areas.map((a) => a.label)).toEqual([
      'Quality of Teaching',
      'Behaviour in School',
      'Sixth Form',
    ]);
    expect(areas.every((a) => a.value >= 1 && a.value <= 4)).toBe(true);
  });

  it('includes the sixth-form judgement where present (was previously dropped)', () => {
    const areas = ofstedLegacyAreas({ sixth_form_provision: 1 });
    expect(areas).toEqual([{ label: 'Sixth Form', value: 1 }]);
  });

  it('includes Early Years for a primary school', () => {
    const areas = ofstedLegacyAreas({ quality_of_education: 2, early_years_provision: 1 });
    expect(areas.map((a) => a.label)).toContain('Early Years (Reception)');
  });
});

describe('buildOfstedListBadge', () => {
  it('returns grade word + year for OEIF Outstanding', () => {
    const badge = buildOfstedListBadge({ ofsted_grade: 1, ofsted_date: '2023-11-15', ofsted_framework: 'OEIF' });
    expect(badge.label).toBe('Outstanding · 2023');
    expect(badge.cssClass).toBe('ofsted1');
  });

  it('returns grade word for each OEIF grade', () => {
    expect(buildOfstedListBadge({ ofsted_grade: 2, ofsted_date: '2022-05-01' }).label).toBe('Good · 2022');
    expect(buildOfstedListBadge({ ofsted_grade: 3, ofsted_date: '2021-01-01' }).label).toBe('Req. Improvement · 2021');
    expect(buildOfstedListBadge({ ofsted_grade: 4, ofsted_date: '2020-03-01' }).label).toBe('Inadequate · 2020');
  });

  it('returns grade word without year when date is missing', () => {
    const badge = buildOfstedListBadge({ ofsted_grade: 2, ofsted_date: null });
    expect(badge.label).toBe('Good');
    expect(badge.cssClass).toBe('ofsted2');
  });

  it('returns a Report Card badge when ofsted_rc_date is present', () => {
    const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_rc_date: '2026-02-03' });
    expect(badge.label).toBe('Report Card · 2026');
    expect(badge.cssClass).toBe('ofstedRc');
  });

  it('a report card wins over a carried-forward legacy grade', () => {
    // The production bug: a report-card school (e.g. Barclay) also carries a
    // carried-forward legacy grade (ofsted_grade), which used to win and label
    // the pin "Outstanding · 2021" instead of "Report Card · 2026".
    const badge = buildOfstedListBadge({
      ofsted_grade: 1,
      ofsted_date: '2021-10-07',
      ofsted_framework: 'Schools - S5',
      ofsted_rc_date: '2026-02-03',
    });
    expect(badge.label).toBe('Report Card · 2026');
    expect(badge.cssClass).toBe('ofstedRc');
  });

  it('returns an "Inspected" badge for an OEIF inspection with no overall grade (post-Sept-2024)', () => {
    // Inspected after Sept 2024: inspection on record (date + framework) but
    // Ofsted no longer issues an overall grade. Must NOT read as "Not yet inspected".
    const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: '2024-11-01', ofsted_framework: 'OEIF' });
    expect(badge.label).toBe('Inspected · 2024');
    expect(badge.cssClass).toBe('ofstedInspected');
  });

  it('returns an "Inspected" badge without a year when the grade is missing and date is absent but a record exists', () => {
    const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_framework: 'OEIF' });
    expect(badge.label).toBe('Inspected');
    expect(badge.cssClass).toBe('ofstedInspected');
  });

  it('returns pending badge when no grade and no inspection on record', () => {
    const badge = buildOfstedListBadge({ ofsted_grade: null, ofsted_date: null, ofsted_framework: null });
    expect(badge.label).toBe('Not yet inspected');
    expect(badge.cssClass).toBe('ofstedPending');
  });

  it('returns pending badge when all fields are undefined', () => {
    const badge = buildOfstedListBadge({});
    expect(badge.label).toBe('Not yet inspected');
    expect(badge.cssClass).toBe('ofstedPending');
  });
});

describe('metricKind', () => {
  it('classifies metrics by key', () => {
    expect(metricKind('rwm_expected_pct')).toBe('percentage');
    expect(metricKind('absence_rate')).toBe('percentage');
    expect(metricKind('reading_progress')).toBe('progress');
    expect(metricKind('progress_8_score')).toBe('progress');
    expect(metricKind('attainment_8_score')).toBe('score');
    expect(metricKind('reading_avg_score')).toBe('score');
  });
});

describe('computeYBounds', () => {
  it('tightens clustered percentages instead of framing 0-100', () => {
    const b = computeYBounds([86, 86, 86, 80, 96], 'percentage');
    expect(b.min).toBeGreaterThanOrEqual(0);
    expect(b.max).toBeLessThanOrEqual(100);
    expect(b.min).toBeGreaterThan(50);
    expect(b.max! - b.min!).toBeGreaterThanOrEqual(10);
  });

  it('never widens percentages beyond 0-100 for non-negative data', () => {
    const b = computeYBounds([2, 5, 98], 'percentage');
    expect(b.min).toBe(0);
    expect(b.max).toBe(100);
  });

  it('does not clamp to zero when pct-named trend data is negative', () => {
    const b = computeYBounds([-12, -3, 4], 'percentage');
    expect(b.min).toBeLessThan(-12);
  });

  it('keeps progress bounds symmetric around zero', () => {
    const b = computeYBounds([-1.2, 0.4, 2.1], 'progress');
    expect(b.min).toBe(-b.max!);
    expect(b.min).toBeLessThanOrEqual(-1.2);
    expect(b.max).toBeGreaterThanOrEqual(2.1);
  });

  it('fits score metrics without a fixed frame', () => {
    const b = computeYBounds([42.3, 48.9, 51.2], 'score');
    expect(b.min).toBeGreaterThanOrEqual(0);
    expect(b.min).toBeLessThanOrEqual(42.3);
    expect(b.max).toBeGreaterThanOrEqual(51.2);
  });

  it('returns empty bounds when there is no numeric data', () => {
    expect(computeYBounds([null, undefined, NaN], 'percentage')).toEqual({});
    expect(computeYBounds([], 'progress')).toEqual({});
  });
});

describe('isProposedToClose', () => {
  const { isProposedToClose } = require('@/lib/utils');

  it('is true only for the exact GIAS proposed-to-close status', () => {
    expect(isProposedToClose({ status: 'Open, but proposed to close' })).toBe(true);
    expect(isProposedToClose({ status: 'Open' })).toBe(false);
    expect(isProposedToClose({ status: null })).toBe(false);
    expect(isProposedToClose({})).toBe(false);
  });
});

describe('isSpecialSchool', () => {
  const { isSpecialSchool } = require('@/lib/utils');

  it('matches every DfE special-school establishment type', () => {
    for (const t of [
      'Community special school',
      'Foundation special school',
      'Academy special converter',
      'Academy special sponsor led',
      'Non-maintained special school',
      'Free schools special',
      'Other independent special school',
    ]) {
      expect(isSpecialSchool({ school_type: t })).toBe(true);
    }
  });

  it('matches pupil referral units and alternative provision', () => {
    expect(isSpecialSchool({ school_type: 'Pupil referral unit' })).toBe(true);
    expect(isSpecialSchool({ school_type: 'Academy alternative provision converter' })).toBe(true);
  });

  it('does not match mainstream types (no false positives)', () => {
    for (const t of [
      'Academy converter',
      'Community school',
      'Voluntary aided school',
      'Free schools',
      'University technical college',
      'Other independent school',
      null,
      undefined,
    ]) {
      expect(isSpecialSchool({ school_type: t })).toBe(false);
    }
  });
});

describe('shortName', () => {
  it('drops the trailing establishment-type words', () => {
    expect(shortName('Barclay Primary School')).toBe('Barclay');
    expect(shortName('Elmhurst Primary School')).toBe('Elmhurst');
    expect(shortName("St Mary's Catholic Primary School")).toBe("St Mary's");
    expect(shortName('Riverside Community Junior School')).toBe('Riverside');
  });

  it('keeps a name that carries no type suffix, capping very long ones', () => {
    expect(shortName('Beaver Road')).toBe('Beaver Road');
    expect(shortName('A'.repeat(30), 10)).toBe('AAAAAAAAA…');
  });
});
