
#include "test.h"
#include "tests/fl/audio/test_helpers.h"
#include "fl/audio/audio.h"
#include "fl/audio/audio_context.h"
#include "fl/audio/fft/fft.h"
#include "fl/audio/signal_conditioner.h"
#include "fl/audio/auto_gain.h"
#include "fl/audio/noise_floor_tracker.h"
#include "fl/audio/audio_processor.h"
#include "fl/audio/detector/beat.h"
#include "fl/audio/detector/energy_analyzer.h"
#include "fl/audio/detector/tempo_analyzer.h"
#include "fl/audio/detector/frequency_bands.h"
#include "fl/audio/detector/vocal.h"
#include "fl/stl/int.h"
#include "fl/stl/function.h"
#include "fl/stl/span.h"
#include "fl/stl/strstream.h"
#include "fl/stl/vector.h"
#include "fl/math/math.h"
#include "fl/stl/shared_ptr.h"
#include "fl/math/math.h"

FL_TEST_FILE(FL_FILEPATH) {

using namespace fl;
using fl::audio::test::makeSample;
using fl::audio::test::makeSilence;
using fl::audio::test::makeDC;
using fl::audio::test::makeMaxAmplitude;

namespace {

static audio::Sample makeSample_Adversarial(float freq, fl::u32 timestamp, float amplitude = 16000.0f) {
    return makeSample(freq, timestamp, amplitude);
}

// Safe NaN check that doesn't rely on isnan
static bool isNaN(float x) {
    return !(x == x);
}

} // anonymous namespace

// F1: audio::fft::FFT Edge Cases

FL_TEST_CASE("Adversarial - audio::fft::FFT with DC-only input produces no spectral peaks") {
    audio::fft::FFT fft;
    fl::vector<fl::i16> dcSignal(512, 10000);  // Pure DC
    audio::fft::Bins bins(16);
    fft.run(dcSignal, &bins);

    // DC should not produce significant energy in frequency bins.
    // With fmin=90 Hz, the lowest CQ bin is closer to DC than with the old
    // fmin=174.6 Hz, so some leakage is expected. The energy should still
    // be modest compared to an in-band sine wave (~30000+ total energy).
    float totalEnergy = 0.0f;
    for (fl::size i = 0; i < bins.raw().size(); ++i) {
        totalEnergy += bins.raw()[i];
    }
    FL_CHECK_LT(totalEnergy, 5000.0f);
}

FL_TEST_CASE("Adversarial - audio::fft::FFT with alternating max samples") {
    audio::fft::FFT fft;
    fl::vector<fl::i16> alternating;
    alternating.reserve(512);
    for (int i = 0; i < 512; ++i) {
        alternating.push_back((i % 2 == 0) ? 32767 : -32768);
    }
    audio::fft::Bins bins(16);
    fft.run(alternating, &bins);

    // Should not crash, bins should have values
    FL_CHECK_GT(bins.raw().size(), 0u);

    // Alternating +-max is essentially Nyquist frequency
    // Energy should be concentrated in the highest bins (if within CQ range)
    // At minimum, it should not produce NaN or Inf
    for (fl::size i = 0; i < bins.raw().size(); ++i) {
        FL_CHECK_FALSE(isNaN(bins.raw()[i]));
    }
}

FL_TEST_CASE("Adversarial - audio::fft::FFT with single impulse") {
    audio::fft::FFT fft;
    fl::vector<fl::i16> impulse(512, 0);
    impulse[256] = 32767; // Center of buffer (Hanning window = 1.0 here)
    audio::fft::Bins bins(16);
    fft.run(impulse, &bins);

    // Impulse should distribute energy across all frequency bins
    FL_CHECK_GT(bins.raw().size(), 0u);
    int nonZeroBins = 0;
    for (fl::size i = 0; i < bins.raw().size(); ++i) {
        if (bins.raw()[i] > 0.0f) nonZeroBins++;
    }
    // Impulse has flat spectrum - should have energy in multiple bins
    FL_CHECK_GT(nonZeroBins, 1);
}

// F2: Signal Conditioner Edge Cases

FL_TEST_CASE("Adversarial - audio::SignalConditioner with max DC offset") {
    audio::SignalConditioner conditioner;
    audio::SignalConditionerConfig config;
    config.enableDCRemoval = true;
    conditioner.configure(config);

    // Feed constant max positive value
    fl::vector<fl::i16> maxDC(512, 32767);
    audio::Sample inputSample(maxDC, 0);
    audio::Sample output = conditioner.processSample(inputSample);

    // After DC removal, output should be near zero (or at least reduced)
    fl::i64 sum = 0;
    for (size i = 0; i < output.pcm().size(); ++i) {
        sum += output.pcm()[i];
    }
    float meanDC = static_cast<float>(sum) / static_cast<float>(output.pcm().size());
    FL_CHECK_LT(fl::abs(meanDC), 16000.0f);
}

FL_TEST_CASE("Adversarial - audio::SignalConditioner with alternating spikes") {
    audio::SignalConditioner conditioner;
    audio::SignalConditionerConfig config;
    config.enableSpikeFilter = true;
    conditioner.configure(config);

    // Every other sample is a spike
    fl::vector<fl::i16> spiky(512);
    for (int i = 0; i < 512; ++i) {
        spiky[i] = (i % 2 == 0) ? 30000 : 0;
    }
    audio::Sample inputSample(spiky, 0);
    audio::Sample output = conditioner.processSample(inputSample);

    fl::i16 maxVal = 0;
    for (size i = 0; i < output.pcm().size(); ++i) {
        if (output.pcm()[i] > maxVal) maxVal = output.pcm()[i];
    }
    FL_CHECK_LT(static_cast<fl::i32>(maxVal), 30000);
}

// F3: audio::AutoGain Edge Cases

FL_TEST_CASE("Adversarial - audio::AutoGain with silence doesn't produce NaN") {
    audio::AutoGain gain;
    audio::AutoGainConfig config;
    config.enabled = true;
    gain.configure(config);

    fl::vector<fl::i16> silence(512, 0);
    audio::Sample inputSample(silence, 0);

    // Feed many frames of silence
    audio::Sample output = inputSample;
    for (int i = 0; i < 50; ++i) {
        output = gain.process(inputSample);
    }

    // Output should be all zeros, no NaN or overflow
    for (size i = 0; i < output.pcm().size(); ++i) {
        FL_CHECK_EQ(output.pcm()[i], 0);
    }

    const auto& stats = gain.getStats();
    FL_CHECK_GT(stats.samplesProcessed, 0u);
}

FL_TEST_CASE("Adversarial - audio::AutoGain with max amplitude clipping") {
    audio::AutoGain gain;
    audio::AutoGainConfig config;
    config.enabled = true;
    config.targetRMSLevel = 20000.0f;  // High target to test clipping
    gain.configure(config);

    // Feed loud signal
    fl::vector<fl::i16> loud;
    loud.reserve(512);
    for (int i = 0; i < 512; ++i) {
        float phase = 2.0f * FL_M_PI * 440.0f * i / 44100.0f;
        loud.push_back(static_cast<fl::i16>(20000.0f * fl::sinf(phase)));
    }
    audio::Sample inputSample(loud, 0);

    audio::Sample output = inputSample;
    for (int iter = 0; iter < 20; ++iter) {
        output = gain.process(inputSample);
    }

    bool hasNonZero = false;
    for (size i = 0; i < output.pcm().size(); ++i) {
        if (output.pcm()[i] != 0) {
            hasNonZero = true;
            break;
        }
    }
    FL_CHECK(hasNonZero);
}

// F4: audio::NoiseFloorTracker Edge Cases

FL_TEST_CASE("Adversarial - audio::NoiseFloorTracker with zero input doesn't go negative") {
    audio::NoiseFloorTracker tracker;
    audio::NoiseFloorTrackerConfig config;
    config.minFloor = 1.0f;
    tracker.configure(config);

    // Feed zero for many frames
    for (int i = 0; i < 200; ++i) {
        tracker.update(0.0f);
    }

    float floor = tracker.getFloor();
    FL_CHECK_GE(floor, config.minFloor);
    FL_CHECK_FALSE(isNaN(floor));
}

FL_TEST_CASE("Adversarial - audio::NoiseFloorTracker with huge value spike") {
    audio::NoiseFloorTracker tracker;

    // Establish low floor
    for (int i = 0; i < 20; ++i) {
        tracker.update(100.0f);
    }

    // Single massive spike
    tracker.update(1000000.0f);

    float afterSpike = tracker.getFloor();

    FL_CHECK_GT(afterSpike, 50.0f);
    FL_CHECK_LT(afterSpike, 1000000.0f);
    FL_CHECK_FALSE(isNaN(afterSpike));
}

// F5: audio::detector::Beat Edge Cases

FL_TEST_CASE("Adversarial - audio::detector::Beat with constant loud signal doesn't spam beats") {
    audio::detector::Beat detector;
    int beatCount = 0;
    detector.onBeat.add([&beatCount]() { beatCount++; });

    auto ctx = fl::make_shared<audio::Context>(makeSilence(0));
    ctx->setSampleRate(44100);

    // Feed constant loud bass tone (no transients, just sustained)
    for (int i = 0; i < 100; ++i) {
        ctx->setSample(makeSample_Adversarial(200.0f, i * 23, 20000.0f));
        ctx->getFFT(16);
        ctx->setFFTHistoryDepth(4);
        detector.update(ctx);
    }

    // Constant signal has no onsets - should produce very few beats
    // (maybe 1-2 at startup as threshold adapts, but not continuous)
    FL_CHECK_LT(beatCount, 10);
}

FL_TEST_CASE("Adversarial - audio::detector::Beat cooldown enforced") {
    audio::detector::Beat detector;
    detector.setThreshold(0.01f);  // Very low threshold

    fl::vector<u32> beatTimestamps;
    detector.onBeat.add([&beatTimestamps]() {
        // We can't easily get timestamp in callback, just count
        beatTimestamps.push_back(static_cast<u32>(beatTimestamps.size()));
    });

    auto ctx = fl::make_shared<audio::Context>(makeSilence(0));
    ctx->setSampleRate(44100);
    ctx->getFFT(16);
    ctx->setFFTHistoryDepth(4);

    // Rapid-fire bass bursts every frame (23ms apart, faster than cooldown)
    for (int i = 0; i < 50; ++i) {
        ctx->setSample(makeSample_Adversarial(200.0f, i * 23, 20000.0f));
        ctx->getFFT(16);
        detector.update(ctx);
    }

    // MIN_BEAT_INTERVAL_MS = 250ms, frames are 23ms apart
    // So max beats in 50*23=1150ms should be about 1150/250 ≈ 4-5
    FL_CHECK_LT(static_cast<int>(beatTimestamps.size()), 10);
}

// F6: audio::detector::EnergyAnalyzer Edge Cases

FL_TEST_CASE("Adversarial - audio::detector::EnergyAnalyzer silence then loud doesn't overflow") {
    audio::detector::EnergyAnalyzer analyzer;

    // Extended silence
    for (int i = 0; i < 50; ++i) {
        auto ctx = fl::make_shared<audio::Context>(makeSilence(i * 23));
        analyzer.update(ctx);
    }

    // Sudden max amplitude
    auto ctx = fl::make_shared<audio::Context>(makeMaxAmplitude(1200));
    analyzer.update(ctx);

    float rms = analyzer.getRMS();
    FL_CHECK_GT(rms, 0.0f);
    FL_CHECK_FALSE(isNaN(rms));

    float normalized = analyzer.getNormalizedRMS();
    FL_CHECK_GE(normalized, 0.0f);
    FL_CHECK_LE(normalized, 1.0f);
}

FL_TEST_CASE("Adversarial - audio::detector::EnergyAnalyzer min never exceeds max after silence") {
    audio::detector::EnergyAnalyzer analyzer;

    // Only silence - tests degenerate min/max initialization
    for (int i = 0; i < 30; ++i) {
        auto ctx = fl::make_shared<audio::Context>(makeSilence(i * 23));
        analyzer.update(ctx);
    }

    // Now a real signal
    auto ctx = fl::make_shared<audio::Context>(makeSample_Adversarial(440.0f, 700, 10000.0f));
    analyzer.update(ctx);

    float minE = analyzer.getMinEnergy();
    float maxE = analyzer.getMaxEnergy();
    // After getting at least one real signal, max should be >= min
    FL_CHECK_GE(maxE, minE);
}

// F7: audio::detector::TempoAnalyzer Edge Cases

FL_TEST_CASE("Adversarial - audio::detector::TempoAnalyzer with random noise doesn't crash") {
    audio::detector::TempoAnalyzer analyzer;
    auto ctx = fl::make_shared<audio::Context>(makeSilence(0));
    ctx->setSampleRate(44100);
    ctx->getFFT(16);
    ctx->setFFTHistoryDepth(4);

    // Feed random-amplitude signals at random-ish intervals
    u32 timestamp = 0;
    for (int i = 0; i < 200; ++i) {
        timestamp += (i % 7) * 5 + 10;  // Irregular intervals
        float amplitude = static_cast<float>((i * 7 + 13) % 20000);
        ctx->setSample(makeSample_Adversarial(200.0f + (i % 5) * 100.0f, timestamp, amplitude));
        ctx->getFFT(16);
        analyzer.update(ctx);
    }

    // Should not crash, BPM should be in valid range
    float bpm = analyzer.getBPM();
    FL_CHECK_GT(bpm, 0.0f);
    FL_CHECK_LT(bpm, 300.0f);
    FL_CHECK_FALSE(isNaN(bpm));
    FL_CHECK_FALSE(isNaN(analyzer.getConfidence()));
    FL_CHECK_FALSE(isNaN(analyzer.getStability()));
}

FL_TEST_CASE("Adversarial - audio::detector::TempoAnalyzer with silence only") {
    audio::detector::TempoAnalyzer analyzer;
    auto ctx = fl::make_shared<audio::Context>(makeSilence(0));
    ctx->setSampleRate(44100);
    ctx->getFFT(16);
    ctx->setFFTHistoryDepth(4);

    for (int i = 0; i < 100; ++i) {
        ctx->setSample(makeSilence(i * 23));
        ctx->getFFT(16);
        analyzer.update(ctx);
    }

    // Should stay at default BPM with zero confidence
    FL_CHECK_EQ(analyzer.getBPM(), 120.0f);
    FL_CHECK_EQ(analyzer.getConfidence(), 0.0f);
}

// F8: audio::Processor Full Pipeline Edge Cases

FL_TEST_CASE("Adversarial - audio::Processor full pipeline with silence") {
    audio::Processor processor;
    float lastEnergy = -1.0f;
    processor.onEnergy([&lastEnergy](float rms) { lastEnergy = rms; });

    // Feed only silence through full pipeline
    for (int i = 0; i < 50; ++i) {
        fl::vector<fl::i16> silence(512, 0);
        audio::Sample sample(silence, i * 23);
        processor.update(sample);
    }

    // Energy callback should have fired with zero or near-zero value
    FL_CHECK_GE(lastEnergy, 0.0f);
    FL_CHECK_LT(lastEnergy, 100.0f);
}

FL_TEST_CASE("Adversarial - audio::Processor rapid sample rate changes") {
    audio::Processor processor;

    // Change sample rate between updates
    processor.setSampleRate(44100);
    audio::Sample s1 = makeSample_Adversarial(440.0f, 100);
    processor.update(s1);

    processor.setSampleRate(22050);
    audio::Sample s2 = makeSample_Adversarial(440.0f, 200);
    processor.update(s2);

    processor.setSampleRate(16000);
    audio::Sample s3 = makeSample_Adversarial(440.0f, 300);
    processor.update(s3);

    // Should not crash, context should be valid
    FL_CHECK(processor.getContext() != nullptr);
    FL_CHECK_EQ(processor.getSampleRate(), 16000);
}

FL_TEST_CASE("Adversarial - audio::Processor reset mid-processing") {
    audio::Processor processor;
    int beatCount = 0;
    processor.onBeat([&beatCount]() { beatCount++; });
    processor.onEnergy([](float) {});  // Register to create detector

    // Process some frames
    for (int i = 0; i < 10; ++i) {
        audio::Sample s = makeSample_Adversarial(200.0f, i * 23, 15000.0f);
        processor.update(s);
    }

    // Reset mid-stream
    processor.reset();

    // Continue processing - should not crash
    for (int i = 0; i < 10; ++i) {
        audio::Sample s = makeSample_Adversarial(200.0f, (10 + i) * 23, 15000.0f);
        processor.update(s);
    }

    FL_CHECK(processor.getContext() != nullptr);
}

// F9: audio::detector::FrequencyBands Edge Cases

FL_TEST_CASE("Adversarial - audio::detector::FrequencyBands with sub-bass frequency") {
    audio::detector::FrequencyBands bands;
    bands.setSampleRate(44100);
    bands.setSmoothing(0.0f);

    // 20 Hz is at the very edge of hearing and bass range
    fl::vector<fl::i16> subBass;
    subBass.reserve(1024);
    for (int i = 0; i < 1024; ++i) {
        float phase = 2.0f * FL_M_PI * 20.0f * i / 44100.0f;
        subBass.push_back(static_cast<fl::i16>(20000.0f * fl::sinf(phase)));
    }

    auto ctx = fl::make_shared<audio::Context>(
        audio::Sample(subBass, 0));
    ctx->setSampleRate(44100);
    bands.update(ctx);

    FL_CHECK_FALSE(isNaN(bands.getBass()));
    FL_CHECK_FALSE(isNaN(bands.getMid()));
    FL_CHECK_FALSE(isNaN(bands.getTreble()));
}

FL_TEST_CASE("Adversarial - audio::detector::FrequencyBands with Nyquist frequency") {
    audio::detector::FrequencyBands bands;
    bands.setSampleRate(44100);
    bands.setSmoothing(0.0f);

    // Near Nyquist frequency (22050 Hz)
    fl::vector<fl::i16> nyquist;
    nyquist.reserve(512);
    for (int i = 0; i < 512; ++i) {
        nyquist.push_back((i % 2 == 0) ? 20000 : -20000);
    }

    auto ctx = fl::make_shared<audio::Context>(
        audio::Sample(nyquist, 0));
    ctx->setSampleRate(44100);
    bands.update(ctx);

    // Should not crash or produce NaN
    FL_CHECK_FALSE(isNaN(bands.getBass()));
    FL_CHECK_FALSE(isNaN(bands.getMid()));
    FL_CHECK_FALSE(isNaN(bands.getTreble()));
}

// F10: audio::detector::Vocal Edge Cases

FL_TEST_CASE("Adversarial - audio::detector::Vocal with DC input") {
    audio::detector::Vocal detector;
    detector.setSampleRate(44100);

    auto ctx = fl::make_shared<audio::Context>(makeDC(10000, 0));
    ctx->setSampleRate(44100);
    ctx->getFFT(128);
    detector.update(ctx);

    // DC should not be detected as vocal
    FL_CHECK_FALSE(detector.isVocal());
    FL_CHECK_FALSE(isNaN(detector.getConfidence()));
}

// ADV-2: INT16_MAX/INT16_MIN Saturation (full-scale square wave)
FL_TEST_CASE("Adversarial - INT16 saturation through pipeline") {
    audio::SignalConditioner conditioner;
    audio::SignalConditionerConfig scConfig;
    scConfig.enableDCRemoval = true;
    scConfig.enableSpikeFilter = true;
    scConfig.enableNoiseGate = true;
    conditioner.configure(scConfig);

    // Full-scale square wave: alternating 32767 and -32768
    audio::Sample saturated = makeMaxAmplitude(1000);
    audio::Sample result = conditioner.processSample(saturated);

    // Should not crash, output should be valid
    FL_CHECK(result.isValid());
    // No NaN in stats
    FL_CHECK_FALSE(isNaN(static_cast<float>(conditioner.getStats().dcOffset)));
}

// ADV-3: Single-Sample Buffer
FL_TEST_CASE("Adversarial - single sample buffer") {
    audio::SignalConditioner conditioner;

    // Create a buffer with exactly 1 sample
    fl::vector<fl::i16> single(1, 5000);
    audio::Sample singleSample(single, 2000);
    audio::Sample result = conditioner.processSample(singleSample);

    // Should not crash
    FL_CHECK(result.isValid());
    FL_CHECK_EQ(result.size(), 1u);
}

// ADV-4: Rapid Config Switching
FL_TEST_CASE("Adversarial - rapid config switching") {
    audio::SignalConditioner conditioner;

    fl::vector<fl::i16> samples(512, 5000);
    audio::Sample audio(samples, 0);

    // Switch configs every frame for 100 frames
    for (int i = 0; i < 100; ++i) {
        audio::SignalConditionerConfig config;
        config.enableDCRemoval = (i % 2 == 0);
        config.enableSpikeFilter = (i % 3 == 0);
        config.enableNoiseGate = (i % 5 == 0);
        config.spikeThreshold = static_cast<fl::i16>(5000 + (i % 10) * 1000);
        conditioner.configure(config);
        audio::Sample result = conditioner.processSample(audio);
        FL_CHECK(result.isValid());
    }
}

// ADV-5: Monotonically Increasing Signal (no periodicity)
FL_TEST_CASE("Adversarial - monotonic signal no false beats") {
    fl::vector<fl::i16> ramp;
    ramp.reserve(512);
    for (int i = 0; i < 512; ++i) {
        // Linear ramp from 0 to 32767
        ramp.push_back(static_cast<fl::i16>((static_cast<fl::i32>(i) * 32767) / 511));
    }
    audio::Sample rampSample(ramp, 3000);

    // Process through signal conditioner
    audio::SignalConditioner conditioner;
    audio::Sample cleaned = conditioner.processSample(rampSample);
    FL_CHECK(cleaned.isValid());
    FL_CHECK_EQ(cleaned.size(), 512u);

    // Process through auto gain
    audio::AutoGain agc;
    audio::Sample gained = agc.process(cleaned);
    FL_CHECK(gained.isValid());

    // No NaN in output
    const auto& pcm = gained.pcm();
    for (fl::size j = 0; j < pcm.size(); ++j) {
        FL_CHECK_GE(pcm[j], -32768);
        FL_CHECK_LE(pcm[j], 32767);
    }
}

FL_TEST_CASE("Adversarial - audio::detector::Vocal with silence") {
    audio::detector::Vocal detector;
    detector.setSampleRate(44100);

    auto ctx = fl::make_shared<audio::Context>(makeSilence(0));
    ctx->setSampleRate(44100);
    ctx->getFFT(128);
    detector.update(ctx);

    FL_CHECK_FALSE(detector.isVocal());
    float conf = detector.getConfidence();
    FL_CHECK_FALSE(isNaN(conf));
}

} // FL_TEST_FILE
