#include "models/SliceModel.h"

#include <QCoreApplication>
#include <QMap>
#include <QSignalSpy>
#include <QString>
#include <cstdio>

using namespace AetherSDR;

static int g_failures = 0;

#define EXPECT_EQ(actual, expected) do { \
    auto a_ = (actual); auto e_ = (expected); \
    if (a_ != e_) { \
        const QString a_str = QString("%1").arg(a_); \
        const QString e_str = QString("%1").arg(e_); \
        std::fprintf(stderr, "FAIL %s:%d  expected %s, got %s\n", \
                     __FILE__, __LINE__, \
                     e_str.toUtf8().constData(), \
                     a_str.toUtf8().constData()); \
        ++g_failures; \
    } \
} while (0)

static QMap<QString, QString> kv(std::initializer_list<std::pair<QString, QString>> pairs)
{
    QMap<QString, QString> m;
    for (const auto& p : pairs) m.insert(p.first, p.second);
    return m;
}

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);

    // ── Fallback: letter() returns 'A' + sliceId when index_letter hasn't
    // arrived yet (older firmware, early in status sequence).
    {
        SliceModel s(2);
        EXPECT_EQ(s.letter(), QString("C"));  // 'A' + 2
    }

    // ── applyStatus("index_letter=A") populates the radio-given letter
    // and emits letterChanged with the resolved value.
    {
        SliceModel s(3);
        QSignalSpy spy(&s, &SliceModel::letterChanged);
        s.applyStatus(kv({{"index_letter", "A"}}));
        EXPECT_EQ(s.letter(), QString("A"));
        EXPECT_EQ(spy.count(), 1);
        EXPECT_EQ(spy.takeFirst().at(0).toString(), QString("A"));
    }

    // ── Re-applying the same letter does NOT re-emit letterChanged.
    {
        SliceModel s(1);
        s.applyStatus(kv({{"index_letter", "B"}}));
        QSignalSpy spy(&s, &SliceModel::letterChanged);
        s.applyStatus(kv({{"index_letter", "B"}}));
        EXPECT_EQ(spy.count(), 0);
    }

    // ── Letter change emits exactly once and the resolved value follows.
    {
        SliceModel s(2);
        s.applyStatus(kv({{"index_letter", "A"}}));
        QSignalSpy spy(&s, &SliceModel::letterChanged);
        s.applyStatus(kv({{"index_letter", "C"}}));
        EXPECT_EQ(s.letter(), QString("C"));
        EXPECT_EQ(spy.count(), 1);
        EXPECT_EQ(spy.takeFirst().at(0).toString(), QString("C"));
    }

    // ── emitLetterRefresh() forces a re-emission with the current value
    // (used when a display preference changes).
    {
        SliceModel s(0);
        s.applyStatus(kv({{"index_letter", "A"}}));
        QSignalSpy spy(&s, &SliceModel::letterChanged);
        s.emitLetterRefresh();
        EXPECT_EQ(spy.count(), 1);
        EXPECT_EQ(spy.takeFirst().at(0).toString(), QString("A"));
    }

    // ── Status messages without index_letter don't disturb the stored
    // letter or emit on letterChanged.
    {
        SliceModel s(2);
        s.applyStatus(kv({{"index_letter", "A"}}));
        QSignalSpy spy(&s, &SliceModel::letterChanged);
        s.applyStatus(kv({{"in_use", "1"}, {"RF_frequency", "14.250"}}));
        EXPECT_EQ(s.letter(), QString("A"));
        EXPECT_EQ(spy.count(), 0);
    }

    // ── External receive replacement (KiwiSDR virtual antenna) owns visible
    // audio pan locally. It must not send audio_pan commands to the Flex radio
    // while Flex audio is silently muted for the same slice.
    {
        SliceModel s(4);
        QStringList commands;
        QObject::connect(&s, &SliceModel::commandReady,
                         [&commands](const QString& cmd) { commands.append(cmd); });
        s.applyStatus(kv({{"audio_pan", "25"}}));
        EXPECT_EQ(s.audioPan(), 25);
        EXPECT_EQ(s.flexAudioPan(), 25);

        QSignalSpy panSpy(&s, &SliceModel::audioPanChanged);
        s.setExternalReceiveAudioReplacementMute(true);
        EXPECT_EQ(commands.join(QStringLiteral("|")),
                  QStringLiteral("slice set 4 audio_mute=1"));
        EXPECT_EQ(s.audioPan(), 25);
        EXPECT_EQ(s.flexAudioPan(), 25);
        EXPECT_EQ(panSpy.count(), 0);
        commands.clear();

        s.setAudioPan(80);
        EXPECT_EQ(s.audioPan(), 80);
        EXPECT_EQ(s.flexAudioPan(), 25);
        EXPECT_EQ(commands.join(QStringLiteral("|")), QString());
        EXPECT_EQ(panSpy.count(), 1);
        EXPECT_EQ(panSpy.takeFirst().at(0).toInt(), 80);

        s.applyStatus(kv({{"audio_pan", "10"}}));
        EXPECT_EQ(s.audioPan(), 80);
        EXPECT_EQ(s.flexAudioPan(), 10);
        EXPECT_EQ(panSpy.count(), 0);

        s.setExternalReceiveAudioReplacementMute(false, false);
        EXPECT_EQ(commands.join(QStringLiteral("|")),
                  QStringLiteral("slice set 4 audio_mute=0"));
        EXPECT_EQ(s.audioPan(), 10);
        EXPECT_EQ(s.flexAudioPan(), 10);
        EXPECT_EQ(panSpy.count(), 1);
        EXPECT_EQ(panSpy.takeFirst().at(0).toInt(), 10);
        commands.clear();

        s.setAudioPan(60);
        EXPECT_EQ(s.audioPan(), 60);
        EXPECT_EQ(s.flexAudioPan(), 60);
        EXPECT_EQ(commands.join(QStringLiteral("|")),
                  QStringLiteral("slice set 4 audio_pan=60"));
    }

    // ── External receive replacement also owns visible AGC and SQL controls.
    // These controls should drive the replacement source, not hidden Flex state.
    {
        SliceModel s(5);
        QStringList commands;
        QObject::connect(&s, &SliceModel::commandReady,
                         [&commands](const QString& cmd) { commands.append(cmd); });
        s.applyStatus(kv({{"agc_mode", "slow"},
                          {"agc_threshold", "40"},
                          {"agc_off_level", "12"},
                          {"squelch", "1"},
                          {"squelch_level", "35"}}));
        EXPECT_EQ(s.agcMode(), QString("slow"));
        EXPECT_EQ(s.agcThreshold(), 40);
        EXPECT_EQ(s.agcOffLevel(), 12);
        EXPECT_EQ(s.squelchOn(), true);
        EXPECT_EQ(s.squelchLevel(), 35);

        QSignalSpy agcModeSpy(&s, &SliceModel::agcModeChanged);
        QSignalSpy agcThresholdSpy(&s, &SliceModel::agcThresholdChanged);
        QSignalSpy agcOffLevelSpy(&s, &SliceModel::agcOffLevelChanged);
        QSignalSpy externalAgcModeSpy(
            &s, &SliceModel::externalReceiveAgcModeChanged);
        QSignalSpy externalAgcThresholdSpy(
            &s, &SliceModel::externalReceiveAgcThresholdChanged);
        QSignalSpy externalAgcOffLevelSpy(
            &s, &SliceModel::externalReceiveAgcOffLevelChanged);
        QSignalSpy externalAutoSquelchSpy(
            &s, &SliceModel::externalReceiveAutoSquelchChanged);
        QSignalSpy squelchSpy(&s, &SliceModel::squelchChanged);
        QSignalSpy externalSquelchSpy(
            &s, &SliceModel::externalReceiveSquelchChanged);
        s.setExternalReceiveAudioReplacementMute(true);
        EXPECT_EQ(commands.join(QStringLiteral("|")),
                  QStringLiteral("slice set 5 audio_mute=1"));
        EXPECT_EQ(s.agcMode(), QString("slow"));
        EXPECT_EQ(s.receiveAgcMode(), QString("med"));
        EXPECT_EQ(s.flexAgcMode(), QString("slow"));
        EXPECT_EQ(s.agcThreshold(), 40);
        EXPECT_EQ(s.receiveAgcThreshold(), -100);
        EXPECT_EQ(s.flexAgcThreshold(), 40);
        EXPECT_EQ(s.agcOffLevel(), 12);
        EXPECT_EQ(s.receiveAgcOffLevel(), 50);
        EXPECT_EQ(s.flexAgcOffLevel(), 12);
        EXPECT_EQ(s.squelchOn(), true);
        EXPECT_EQ(s.receiveSquelchOn(), false);
        EXPECT_EQ(s.externalReceiveAutoSquelchOn(), false);
        EXPECT_EQ(s.flexSquelchOn(), true);
        EXPECT_EQ(s.squelchLevel(), 35);
        EXPECT_EQ(s.receiveSquelchLevel(), 0);
        EXPECT_EQ(s.flexSquelchLevel(), 35);
        EXPECT_EQ(agcModeSpy.count(), 0);
        EXPECT_EQ(agcThresholdSpy.count(), 0);
        EXPECT_EQ(agcOffLevelSpy.count(), 0);
        EXPECT_EQ(externalAgcModeSpy.count(), 1);
        EXPECT_EQ(externalAgcModeSpy.takeFirst().at(0).toString(), QString("med"));
        EXPECT_EQ(externalAgcThresholdSpy.count(), 1);
        EXPECT_EQ(externalAgcThresholdSpy.takeFirst().at(0).toInt(), -100);
        EXPECT_EQ(externalAgcOffLevelSpy.count(), 1);
        EXPECT_EQ(externalAgcOffLevelSpy.takeFirst().at(0).toInt(), 50);
        EXPECT_EQ(squelchSpy.count(), 0);
        EXPECT_EQ(externalSquelchSpy.count(), 1);
        EXPECT_EQ(externalSquelchSpy.takeFirst().at(0).toBool(), false);
        EXPECT_EQ(externalAutoSquelchSpy.count(), 0);
        commands.clear();

        s.setExternalReceiveAutoSquelch(true);
        EXPECT_EQ(s.externalReceiveAutoSquelchOn(), true);
        EXPECT_EQ(commands.join(QStringLiteral("|")), QString());
        EXPECT_EQ(externalAutoSquelchSpy.count(), 1);
        EXPECT_EQ(externalAutoSquelchSpy.takeFirst().at(0).toBool(), true);

        s.setAgcMode(QStringLiteral("off"));
        s.setAgcThreshold(-40);
        s.setAgcOffLevel(44);
        s.setSquelch(false, 66);
        EXPECT_EQ(s.agcMode(), QString("slow"));
        EXPECT_EQ(s.receiveAgcMode(), QString("off"));
        EXPECT_EQ(s.flexAgcMode(), QString("slow"));
        EXPECT_EQ(s.agcThreshold(), 40);
        EXPECT_EQ(s.receiveAgcThreshold(), -40);
        EXPECT_EQ(s.flexAgcThreshold(), 40);
        EXPECT_EQ(s.agcOffLevel(), 12);
        EXPECT_EQ(s.receiveAgcOffLevel(), 44);
        EXPECT_EQ(s.flexAgcOffLevel(), 12);
        EXPECT_EQ(s.squelchOn(), true);
        EXPECT_EQ(s.receiveSquelchOn(), false);
        EXPECT_EQ(s.externalReceiveAutoSquelchOn(), true);
        EXPECT_EQ(s.flexSquelchOn(), true);
        EXPECT_EQ(s.squelchLevel(), 35);
        EXPECT_EQ(s.receiveSquelchLevel(), 66);
        EXPECT_EQ(s.flexSquelchLevel(), 35);
        EXPECT_EQ(commands.join(QStringLiteral("|")), QString());
        EXPECT_EQ(agcModeSpy.count(), 0);
        EXPECT_EQ(agcThresholdSpy.count(), 0);
        EXPECT_EQ(agcOffLevelSpy.count(), 0);
        EXPECT_EQ(externalAgcModeSpy.count(), 1);
        EXPECT_EQ(externalAgcModeSpy.takeFirst().at(0).toString(), QString("off"));
        EXPECT_EQ(externalAgcThresholdSpy.count(), 1);
        EXPECT_EQ(externalAgcThresholdSpy.takeFirst().at(0).toInt(), -40);
        EXPECT_EQ(externalAgcOffLevelSpy.count(), 1);
        EXPECT_EQ(externalAgcOffLevelSpy.takeFirst().at(0).toInt(), 44);
        EXPECT_EQ(squelchSpy.count(), 0);
        EXPECT_EQ(externalSquelchSpy.count(), 1);
        EXPECT_EQ(externalSquelchSpy.takeFirst().at(0).toBool(), false);
        commands.clear();

        s.applyStatus(kv({{"agc_mode", "fast"},
                          {"agc_threshold", "90"},
                          {"agc_off_level", "8"},
                          {"squelch", "1"},
                          {"squelch_level", "12"}}));
        EXPECT_EQ(s.agcMode(), QString("fast"));
        EXPECT_EQ(s.receiveAgcMode(), QString("off"));
        EXPECT_EQ(s.flexAgcMode(), QString("fast"));
        EXPECT_EQ(s.agcThreshold(), 90);
        EXPECT_EQ(s.receiveAgcThreshold(), -40);
        EXPECT_EQ(s.flexAgcThreshold(), 90);
        EXPECT_EQ(s.agcOffLevel(), 8);
        EXPECT_EQ(s.receiveAgcOffLevel(), 44);
        EXPECT_EQ(s.flexAgcOffLevel(), 8);
        EXPECT_EQ(s.squelchOn(), true);
        EXPECT_EQ(s.receiveSquelchOn(), false);
        EXPECT_EQ(s.externalReceiveAutoSquelchOn(), true);
        EXPECT_EQ(s.flexSquelchOn(), true);
        EXPECT_EQ(s.squelchLevel(), 12);
        EXPECT_EQ(s.receiveSquelchLevel(), 66);
        EXPECT_EQ(s.flexSquelchLevel(), 12);
        EXPECT_EQ(agcModeSpy.count(), 1);
        EXPECT_EQ(agcModeSpy.takeFirst().at(0).toString(), QString("fast"));
        EXPECT_EQ(agcThresholdSpy.count(), 1);
        EXPECT_EQ(agcThresholdSpy.takeFirst().at(0).toInt(), 90);
        EXPECT_EQ(agcOffLevelSpy.count(), 1);
        EXPECT_EQ(agcOffLevelSpy.takeFirst().at(0).toInt(), 8);
        EXPECT_EQ(squelchSpy.count(), 1);
        EXPECT_EQ(squelchSpy.takeFirst().at(0).toBool(), true);
        EXPECT_EQ(externalAgcModeSpy.count(), 0);
        EXPECT_EQ(externalAgcThresholdSpy.count(), 0);
        EXPECT_EQ(externalAgcOffLevelSpy.count(), 0);
        EXPECT_EQ(externalSquelchSpy.count(), 0);

        s.setExternalReceiveAudioReplacementMute(false, false);
        EXPECT_EQ(commands.join(QStringLiteral("|")),
                  QStringLiteral("slice set 5 audio_mute=0"));
        EXPECT_EQ(s.agcMode(), QString("fast"));
        EXPECT_EQ(s.agcThreshold(), 90);
        EXPECT_EQ(s.agcOffLevel(), 8);
        EXPECT_EQ(s.squelchOn(), true);
        EXPECT_EQ(s.squelchLevel(), 12);
        EXPECT_EQ(s.externalReceiveAutoSquelchOn(), false);
        EXPECT_EQ(agcModeSpy.count(), 1);
        EXPECT_EQ(agcModeSpy.takeFirst().at(0).toString(), QString("fast"));
        EXPECT_EQ(agcThresholdSpy.count(), 1);
        EXPECT_EQ(agcThresholdSpy.takeFirst().at(0).toInt(), 90);
        EXPECT_EQ(agcOffLevelSpy.count(), 1);
        EXPECT_EQ(agcOffLevelSpy.takeFirst().at(0).toInt(), 8);
        EXPECT_EQ(squelchSpy.count(), 1);
        EXPECT_EQ(squelchSpy.takeFirst().at(0).toBool(), true);
        EXPECT_EQ(squelchSpy.count(), 0);
        EXPECT_EQ(externalSquelchSpy.count(), 0);
        EXPECT_EQ(externalAutoSquelchSpy.count(), 1);
        EXPECT_EQ(externalAutoSquelchSpy.takeFirst().at(0).toBool(), false);
        commands.clear();

        s.setAgcMode(QStringLiteral("med"));
        s.setAgcThreshold(20);
        s.setAgcOffLevel(30);
        s.setSquelch(false, 22);
        EXPECT_EQ(commands.join(QStringLiteral("|")),
                  QStringLiteral("slice set 5 agc_mode=med|"
                                 "slice set 5 agc_threshold=20|"
                                 "slice set 5 agc_off_level=30|"
                                 "slice set 5 squelch=0|"
                                 "slice set 5 squelch_level=22"));
    }

    if (g_failures == 0) {
        std::printf("slice_model_letter_test: all checks passed\n");
        return 0;
    }
    std::printf("slice_model_letter_test: %d failure(s)\n", g_failures);
    return 1;
}
