using DeepDrftContent.Processors;

namespace DeepDrftTests;

/// <summary>
/// Behavioral tests for the RMS loudness algorithm. The algorithm reduces raw PCM to a
/// fixed-length, peak-normalized loudness envelope; these tests anchor the contract that a loud
/// region reads as high buckets, silence reads as zero, and output is bounded to [0, 1].
/// </summary>
[TestFixture]
public class RmsLoudnessAlgorithmTests
{
    private const int SampleRate = 44100;
    private const int Channels = 1;
    private const int BitsPerSample = 16;

    private RmsLoudnessAlgorithm _algorithm = null!;

    [SetUp]
    public void SetUp() => _algorithm = new RmsLoudnessAlgorithm();

    [Test]
    public void Compute_LoudSecondHalf_ProducesHigherBucketsThanSilentFirstHalf()
    {
        const int frames = 44100; // 1 second
        var pcm = new byte[frames * 2]; // 16-bit mono

        // First half silent (zeros); second half a full-scale square wave.
        for (var i = frames / 2; i < frames; i++)
        {
            WriteInt16(pcm, i * 2, short.MaxValue);
        }

        var profile = _algorithm.Compute(pcm, Channels, SampleRate, BitsPerSample, bucketCount: 16);

        var silentAverage = profile.Take(8).Average();
        var loudAverage = profile.Skip(8).Average();

        Assert.That(silentAverage, Is.LessThan(0.01), "silent region should read near zero");
        Assert.That(loudAverage, Is.GreaterThan(0.9), "loud region should read near peak after normalization");
        Assert.That(loudAverage, Is.GreaterThan(silentAverage * 10),
            "loud region must be significantly higher than the silent region");
    }

    [Test]
    public void Compute_AllSilence_ReturnsAllZeros()
    {
        var pcm = new byte[44100 * 2]; // all zeros, 16-bit mono

        var profile = _algorithm.Compute(pcm, Channels, SampleRate, BitsPerSample, bucketCount: 32);

        Assert.That(profile, Has.Length.EqualTo(32));
        Assert.That(profile, Is.All.EqualTo(0.0));
    }

    [Test]
    public void Compute_AllValues_AreWithinUnitRangeAndPeakIsOne()
    {
        const int frames = 8192;
        var pcm = new byte[frames * 2];

        // Ramp amplitude across the signal so buckets differ and exactly one reaches peak.
        for (var i = 0; i < frames; i++)
        {
            var amplitude = (short)(short.MaxValue * ((double)i / frames));
            WriteInt16(pcm, i * 2, amplitude);
        }

        var profile = _algorithm.Compute(pcm, Channels, SampleRate, BitsPerSample, bucketCount: 64);

        Assert.That(profile, Is.All.InRange(0.0, 1.0));
        Assert.That(profile.Max(), Is.EqualTo(1.0).Within(1e-9), "peak normalization must put the loudest bucket at 1");
    }

    [Test]
    public void Compute_AveragesStereoChannelsToMono()
    {
        const int frames = 4096;
        var pcm = new byte[frames * 2 * 2]; // 16-bit, 2 channels

        // Left at full scale, right at silence — mono average is half scale, non-zero.
        for (var i = 0; i < frames; i++)
        {
            WriteInt16(pcm, i * 4, short.MaxValue); // left
            WriteInt16(pcm, i * 4 + 2, 0);          // right
        }

        var profile = _algorithm.Compute(pcm, channels: 2, SampleRate, BitsPerSample, bucketCount: 8);

        Assert.That(profile.Max(), Is.GreaterThan(0.0), "mixed-channel signal must not read as silence");
    }

    private static void WriteInt16(byte[] buffer, int offset, short value)
    {
        buffer[offset] = (byte)(value & 0xFF);
        buffer[offset + 1] = (byte)((value >> 8) & 0xFF);
    }
}
