import {
  buildWaveformPeakSet,
  generateWaveformPeakSetFromBuffer,
  generateWaveformPeaksFromBuffer,
  generateWaveformPeaksFromSeed,
  getDetailedWaveformPeakCount,
  normalizeWaveformPeaks,
} from './waveform.util';

const expectDisplaySafeWaveform = (peaks: number[], expectedLength = 160) => {
  expect(peaks).toHaveLength(expectedLength);
  expect(peaks.every((value) => Number.isInteger(value))).toBe(true);
  expect(peaks.every((value) => value >= 0 && value <= 100)).toBe(true);
};

const createPcmWavBuffer = (amplitudeByFrame: (frame: number) => number, frames = 8000) => {
  const sampleRate = 8000;
  const channels = 1;
  const bitsPerSample = 16;
  const bytesPerSample = bitsPerSample / 8;
  const dataSize = frames * channels * bytesPerSample;
  const buffer = Buffer.alloc(44 + dataSize);

  buffer.write('RIFF', 0, 'ascii');
  buffer.writeUInt32LE(36 + dataSize, 4);
  buffer.write('WAVE', 8, 'ascii');
  buffer.write('fmt ', 12, 'ascii');
  buffer.writeUInt32LE(16, 16);
  buffer.writeUInt16LE(1, 20);
  buffer.writeUInt16LE(channels, 22);
  buffer.writeUInt32LE(sampleRate, 24);
  buffer.writeUInt32LE(sampleRate * channels * bytesPerSample, 28);
  buffer.writeUInt16LE(channels * bytesPerSample, 32);
  buffer.writeUInt16LE(bitsPerSample, 34);
  buffer.write('data', 36, 'ascii');
  buffer.writeUInt32LE(dataSize, 40);

  for (let frame = 0; frame < frames; frame += 1) {
    const tone = Math.sin((frame / sampleRate) * Math.PI * 2 * 440);
    const amplitude = amplitudeByFrame(frame);
    const sample = Math.max(-32768, Math.min(32767, Math.round(tone * amplitude * 32767)));
    buffer.writeInt16LE(sample, 44 + frame * bytesPerSample);
  }

  return buffer;
};

describe('waveform util', () => {
  const originalAudioWaveformPeaks = process.env.AUDIO_WAVEFORM_PEAKS;

  beforeEach(() => {
    delete process.env.AUDIO_WAVEFORM_PEAKS;
  });

  afterAll(() => {
    if (typeof originalAudioWaveformPeaks === 'undefined') {
      delete process.env.AUDIO_WAVEFORM_PEAKS;
      return;
    }

    process.env.AUDIO_WAVEFORM_PEAKS = originalAudioWaveformPeaks;
  });

  it('returns exactly 160 preview values by default', () => {
    expectDisplaySafeWaveform(normalizeWaveformPeaks([10, 20, 30, 40]));
  });

  it('clamps values between 0 and 100', () => {
    const peaks = normalizeWaveformPeaks([-20, 10.4, 55.7, 250, Number.NaN]);

    expectDisplaySafeWaveform(peaks);
  });

  it('resamples short input to 160 values', () => {
    const peaks = normalizeWaveformPeaks([5, 20, 60, 90, 30, 10]);

    expectDisplaySafeWaveform(peaks);
  });

  it('downsamples long input to 160 values', () => {
    const input = Array.from({ length: 200 }, (_, index) => index + 1);

    expectDisplaySafeWaveform(normalizeWaveformPeaks(input));
  });

  it('returns fallback 160 values for empty input', () => {
    const peaks = normalizeWaveformPeaks([]);

    expectDisplaySafeWaveform(peaks);
    expect(new Set(peaks).size).toBeGreaterThan(1);
  });

  it('uses configured values only within the Flutter display-safe range', () => {
    process.env.AUDIO_WAVEFORM_PEAKS = '180';
    expectDisplaySafeWaveform(normalizeWaveformPeaks([1, 2, 3]), 180);

    process.env.AUDIO_WAVEFORM_PEAKS = '100';
    expectDisplaySafeWaveform(normalizeWaveformPeaks([1, 2, 3]), 160);
  });

  it('generates display-safe peaks from audio buffers and seeds', () => {
    expectDisplaySafeWaveform(generateWaveformPeaksFromBuffer(Buffer.from('fake audio bytes')));
    expectDisplaySafeWaveform(generateWaveformPeaksFromSeed('audio-url'));
  });

  it('generates varied normalized peaks from actual WAV PCM samples', () => {
    const wavBuffer = createPcmWavBuffer((frame) => 0.12 + (frame / 7999) * 0.78);
    const peaks = generateWaveformPeaksFromBuffer(wavBuffer);

    expectDisplaySafeWaveform(peaks);
    expect(new Set(peaks).size).toBeGreaterThan(8);
    expect(peaks.slice(0, 40).some((value) => value < 60)).toBe(true);
    expect(peaks.slice(60).some((value) => value > 80)).toBe(true);
  });

  it('keeps detailed peak counts within Flutter response limits', () => {
    expect(getDetailedWaveformPeakCount(12)).toBe(500);
    expect(getDetailedWaveformPeakCount(130)).toBeGreaterThanOrEqual(600);
    expect(getDetailedWaveformPeakCount(130)).toBeLessThanOrEqual(1000);
    expect(getDetailedWaveformPeakCount(600)).toBe(1000);
  });

  it('keeps silent WAV samples low instead of amplifying them into a fake waveform', () => {
    const wavBuffer = createPcmWavBuffer(() => 0);
    const peaks = generateWaveformPeaksFromBuffer(wavBuffer);

    expectDisplaySafeWaveform(peaks);
    expect(peaks.every((value) => value === 0)).toBe(true);
  });

  it('generates detailed peaks for long audio without changing preview size', () => {
    const detailedCount = getDetailedWaveformPeakCount(180);
    const waveformSet = buildWaveformPeakSet(
      Array.from({ length: 1400 }, (_, index) => (index % 17) + 1),
      180,
    );

    expect(detailedCount).toBeGreaterThanOrEqual(600);
    expect(detailedCount).toBeLessThanOrEqual(1000);
    expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 160);
    expectDisplaySafeWaveform(waveformSet.waveformPeaks, 160);
    expect(waveformSet.waveformPeaks).toEqual(waveformSet.waveformPeaksPreview);
    expect(waveformSet.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(600);
    expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1000);
    expect(waveformSet.waveformPeaksDetailed.every((value) => Number.isInteger(value))).toBe(true);
    expect(waveformSet.waveformPeaksDetailed.every((value) => value >= 0 && value <= 100)).toBe(true);
    expect(new Set(waveformSet.waveformPeaksDetailed).size).toBeGreaterThan(1);
  });

  it('generates preview and detailed peak sets from buffers', () => {
    const wavBuffer = createPcmWavBuffer((frame) => 0.15 + (frame / 7999) * 0.7);
    const waveformSet = generateWaveformPeakSetFromBuffer(wavBuffer, 240);

    expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 160);
    expect(waveformSet.waveformPeaks).toEqual(waveformSet.waveformPeaksPreview);
    expect(waveformSet.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(600);
    expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1000);
    expect(waveformSet.waveformPeaksDetailed.every((value) => Number.isInteger(value))).toBe(true);
    expect(new Set(waveformSet.waveformPeaksDetailed).size).toBeGreaterThan(8);
  });
});
