import { describe, expect, it } from 'vitest';
import {
  aggregatePcmPeaks,
  createWaveformCacheKey,
  deserializeWaveformPeaks,
  serializeWaveformPeaks,
  WaveformDataError,
  WAVEFORM_PEAKS_MEDIA_TYPE,
} from '../../src/waveform';

describe('waveform peak cache format', () => {
  it('round-trips versioned arrays without sharing input storage', () => {
    const peaks = createPeaks();
    const serialized = serializeWaveformPeaks(peaks);
    const restored = deserializeWaveformPeaks(serialized);

    expect(WAVEFORM_PEAKS_MEDIA_TYPE).toBe(
      'application/vnd.add-ideas.waveform-peaks'
    );
    expect(restored).not.toBe(peaks);
    expect(restored.minimum.buffer).not.toBe(peaks.minimum.buffer);
    expect(restored).toMatchObject({
      version: 1,
      sourceEncoding: 's16le',
      sampleRate: 8_000,
      sampleCount: 6,
      durationSeconds: 6 / 8_000,
      bucketCount: 3,
    });
    expect([...restored.minimum]).toEqual([...peaks.minimum]);
    expect([...restored.maximum]).toEqual([...peaks.maximum]);
    expect([...restored.rms]).toEqual([...peaks.rms]);
  });

  it('detects payload corruption, truncation, and unknown versions', () => {
    const serialized = serializeWaveformPeaks(createPeaks());

    const corrupt = serialized.slice();
    const lastIndex = corrupt.length - 1;
    corrupt[lastIndex] = (corrupt[lastIndex] ?? 0) ^ 0xff;
    expect(() => deserializeWaveformPeaks(corrupt)).toThrow(/checksum/u);

    const corruptHeader = serialized.slice();
    new DataView(corruptHeader.buffer).setUint32(12, 44_100, true);
    expect(() => deserializeWaveformPeaks(corruptHeader)).toThrow(/checksum/u);

    expect(() =>
      deserializeWaveformPeaks(serialized.subarray(0, serialized.length - 1))
    ).toThrow(/payload size/u);

    const unknownVersion = serialized.slice();
    new DataView(unknownVersion.buffer).setUint16(4, 999, true);
    expect(() => deserializeWaveformPeaks(unknownVersion)).toThrow(
      WaveformDataError
    );
  });

  it('builds deterministic cache keys from source, stream, and settings', () => {
    const base = {
      sourceSize: 1_234_567,
      sourceLastModified: 1_720_000_000_000,
      streamIndex: 1,
      sampleRate: 8_000,
      bucketCount: 2_048,
      encoding: 's16le' as const,
      partialHash: 'sha256-deadbeef',
    };
    const first = createWaveformCacheKey(base);
    const second = createWaveformCacheKey({ ...base });
    const changed = createWaveformCacheKey({ ...base, streamIndex: 2 });

    expect(first).toBe(second);
    expect(first).toMatch(/^waveform-peaks-[a-f0-9]{16}$/u);
    expect(changed).not.toBe(first);
  });

  it('rejects unsafe or incomplete cache identities', () => {
    expect(() =>
      createWaveformCacheKey({
        sourceSize: 1,
        sourceLastModified: 2,
        streamIndex: 0,
        sampleRate: 8_000,
        bucketCount: 100,
        encoding: 's16le',
        partialHash: '../../source',
      })
    ).toThrow(TypeError);
  });
});

function createPeaks() {
  return aggregatePcmPeaks(
    {
      container: 'raw',
      data: new Int16Array([-20_000, 10_000, -1_000, 2_000, -30_000, 20_000]),
      encoding: 's16le',
      sampleRate: 8_000,
    },
    { bucketCount: 3 }
  );
}
