// Copyright (c) 2026 Sebastian Palencsar
// SPDX-License-Identifier: GPL-3.0-or-later

#include <QtTest>
#include <QApplication>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QTemporaryDir>
#include <QCryptographicHash>
#include <QEventLoop>
#include <QTimer>

#include "radiobackend.h"

class RadioBackendPlaybackTest : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();
    void init();
    void history_next_navigates_older_entries();
    void resume_disables_list_navigation();
    void station_list_next_uses_filtered_index();
    void history_next_does_not_jump_to_station_list();
    void cached_load_keeps_error_clear_after_network_failure();
    void list_reload_resyncs_index_for_playing_station();
    void list_next_disabled_when_playing_station_not_in_new_list();
    void manual_station_rejects_unsafe_url_scheme();
    void manual_station_accepts_http_and_https();
};

namespace {
QTemporaryDir *s_tempHome = nullptr;

QString cachePathForEndpoint(const QString &endpoint)
{
    const QString cacheDir = QDir::homePath() + QStringLiteral("/.cache/bearwave/api_cache");
    const QString hash = QString(QCryptographicHash::hash(endpoint.toUtf8(), QCryptographicHash::Md5).toHex());
    return cacheDir + QStringLiteral("/") + hash + QStringLiteral(".json");
}

void writeStationCache(const QString &endpoint, const QString &name,
                       const QString &url = QString(), const QString &uuid = QString())
{
    QDir().mkpath(QDir::homePath() + QStringLiteral("/.cache/bearwave/api_cache"));

    QJsonObject station;
    station.insert(QStringLiteral("name"), name);
    station.insert(QStringLiteral("url_resolved"),
                   url.isEmpty() ? QStringLiteral("http://example.com/") + name : url);
    station.insert(QStringLiteral("country"), QStringLiteral("Germany"));
    station.insert(QStringLiteral("stationuuid"), uuid.isEmpty() ? name : uuid);

    QJsonArray array;
    array.append(station);

    QFile file(cachePathForEndpoint(endpoint));
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        return;
    }
    file.write(QJsonDocument(array).toJson());
    file.close();
}

void writeStationListCache(const QString &endpoint, const QJsonArray &stations)
{
    QDir().mkpath(QDir::homePath() + QStringLiteral("/.cache/bearwave/api_cache"));
    QFile file(cachePathForEndpoint(endpoint));
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        return;
    }
    file.write(QJsonDocument(stations).toJson());
    file.close();
}

int visibleIndexForUrl(RadioBackend &backend, const QString &url)
{
    const QList<QObject*> stations = backend.stations();
    for (int i = 0; i < stations.size(); ++i) {
        QObject *station = stations[i];
        const QString resolved = station->property("urlResolved").toString();
        if (resolved == url) {
            return i;
        }
    }
    return -1;
}
}

void RadioBackendPlaybackTest::initTestCase()
{
    s_tempHome = new QTemporaryDir();
    QVERIFY(s_tempHome->isValid());
    qputenv("HOME", s_tempHome->path().toUtf8());

    const QString configDir = QDir::homePath() + QStringLiteral("/.config/bearwave");
    QDir().mkpath(configDir);

    QJsonObject state;
    state.insert(QStringLiteral("lastStationName"), QStringLiteral("Station A"));
    state.insert(QStringLiteral("lastStationUrl"), QStringLiteral("http://example.com/a"));
    state.insert(QStringLiteral("volume"), 0.5);

    QJsonArray recent;
    recent.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Station A")},
        {QStringLiteral("uuid"), QStringLiteral("uuid-a")},
        {QStringLiteral("urlResolved"), QStringLiteral("http://example.com/a")}
    });
    recent.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Station B")},
        {QStringLiteral("uuid"), QStringLiteral("uuid-b")},
        {QStringLiteral("urlResolved"), QStringLiteral("http://example.com/b")}
    });
    recent.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Station C")},
        {QStringLiteral("uuid"), QStringLiteral("uuid-c")},
        {QStringLiteral("urlResolved"), QStringLiteral("http://example.com/c")}
    });
    state.insert(QStringLiteral("recentStations"), recent);

    QFile file(configDir + QStringLiteral("/state.json"));
    QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Truncate));
    file.write(QJsonDocument(state).toJson());
    file.close();
}

void RadioBackendPlaybackTest::init()
{
    initTestCase();
}

void RadioBackendPlaybackTest::history_next_navigates_older_entries()
{
    RadioBackend backend;

    QVERIFY(backend.playRecentByUuid(QStringLiteral("uuid-b"), QString()));
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/b"));
    QVERIFY(backend.hasNextStation());
    QVERIFY(!backend.hasPreviousStation());

    backend.playNextStation();
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/a"));
    QVERIFY(backend.hasPreviousStation());
}

void RadioBackendPlaybackTest::resume_disables_list_navigation()
{
    RadioBackend backend;

    backend.resumeLastStation();
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/a"));
    QVERIFY(!backend.hasNextStation());
    QVERIFY(!backend.hasPreviousStation());

    backend.playNextStation();
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/a"));
}

void RadioBackendPlaybackTest::station_list_next_uses_filtered_index()
{
    RadioBackend backend;

    backend.addManualStation(QStringLiteral("Station One"), QStringLiteral("http://example.com/one"), QStringLiteral("DE"));
    backend.addManualStation(QStringLiteral("Station Two"), QStringLiteral("http://example.com/two"), QStringLiteral("DE"));

    backend.playStation(0);
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/two"));
    QVERIFY(backend.hasNextStation());

    backend.playNextStation();
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/one"));
}

void RadioBackendPlaybackTest::history_next_does_not_jump_to_station_list()
{
    RadioBackend backend;

    // Loaded station list present, but playback started from history (not playStation()).
    backend.addManualStation(QStringLiteral("Station X"), QStringLiteral("http://example.com/x"), QStringLiteral("DE"));
    backend.addManualStation(QStringLiteral("Station Y"), QStringLiteral("http://example.com/y"), QStringLiteral("DE"));

    QVERIFY(backend.playRecentByUuid(QStringLiteral("uuid-c"), QString()));
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/c"));

    backend.playNextStation();
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/a"));
    QVERIFY(backend.currentStationUrl() != QStringLiteral("http://example.com/x"));
    QVERIFY(backend.currentStationUrl() != QStringLiteral("http://example.com/y"));
}

void RadioBackendPlaybackTest::cached_load_keeps_error_clear_after_network_failure()
{
    writeStationCache(QStringLiteral("/stations/topvote/100"), QStringLiteral("Cached Top Station"));

    RadioBackend backend;
    QVERIFY(QFile::exists(QDir::homePath() + QStringLiteral("/.cache/bearwave/api_cache")));
    backend.loadTopStations();

    QTRY_VERIFY_WITH_TIMEOUT(backend.stations().size() > 0, 2000);
    QCOMPARE(backend.lastError(), QString());

    QEventLoop loop;
    QTimer::singleShot(11000, &loop, &QEventLoop::quit);
    loop.exec();

    QCOMPARE(backend.lastError(), QString());
    QVERIFY(backend.stations().size() > 0);
}

void RadioBackendPlaybackTest::list_reload_resyncs_index_for_playing_station()
{
    const QString topEndpoint = QStringLiteral("/stations/topvote/100");
    const QString deEndpoint = QStringLiteral("/stations/bycountrycodeexact/DE?limit=50&order=votes&reverse=true");
    const QString sharedUrl = QStringLiteral("http://example.com/shared");

    QJsonArray topStations;
    topStations.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Alpha Top")},
        {QStringLiteral("url_resolved"), QStringLiteral("http://example.com/alpha")},
        {QStringLiteral("country"), QStringLiteral("Global")},
        {QStringLiteral("stationuuid"), QStringLiteral("uuid-alpha")}
    });
    topStations.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Shared Station")},
        {QStringLiteral("url_resolved"), sharedUrl},
        {QStringLiteral("country"), QStringLiteral("Global")},
        {QStringLiteral("stationuuid"), QStringLiteral("uuid-shared")}
    });
    topStations.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Zulu Top")},
        {QStringLiteral("url_resolved"), QStringLiteral("http://example.com/zulu")},
        {QStringLiteral("country"), QStringLiteral("Global")},
        {QStringLiteral("stationuuid"), QStringLiteral("uuid-zulu")}
    });
    writeStationListCache(topEndpoint, topStations);

    QJsonArray deStations;
    deStations.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("DE Only")},
        {QStringLiteral("url_resolved"), QStringLiteral("http://example.com/deonly")},
        {QStringLiteral("country"), QStringLiteral("Germany")},
        {QStringLiteral("stationuuid"), QStringLiteral("uuid-deonly")}
    });
    deStations.append(QJsonObject{
        {QStringLiteral("name"), QStringLiteral("Shared Station")},
        {QStringLiteral("url_resolved"), sharedUrl},
        {QStringLiteral("country"), QStringLiteral("Germany")},
        {QStringLiteral("stationuuid"), QStringLiteral("uuid-shared")}
    });
    writeStationListCache(deEndpoint, deStations);

    RadioBackend backend;
    backend.loadTopStations();
    QTRY_VERIFY_WITH_TIMEOUT(backend.stations().size() == 3, 2000);

    const int sharedTopIndex = visibleIndexForUrl(backend, sharedUrl);
    QVERIFY(sharedTopIndex >= 0);
    backend.playStation(sharedTopIndex);
    QCOMPARE(backend.currentStationUrl(), sharedUrl);
    QVERIFY(backend.hasNextStation());

    backend.loadGermanStations();
    QTRY_VERIFY_WITH_TIMEOUT(backend.stations().size() == 2, 2000);
    QCOMPARE(backend.currentStationUrl(), sharedUrl);
    QVERIFY(!backend.hasNextStation());
    QVERIFY(backend.hasPreviousStation());

    backend.playNextStation();
    QCOMPARE(backend.currentStationUrl(), sharedUrl);

    backend.playPreviousStation();
    QCOMPARE(backend.currentStationUrl(), QStringLiteral("http://example.com/deonly"));
}

void RadioBackendPlaybackTest::list_next_disabled_when_playing_station_not_in_new_list()
{
    const QString topEndpoint = QStringLiteral("/stations/topvote/100");
    const QString deEndpoint = QStringLiteral("/stations/bycountrycodeexact/DE?limit=50&order=votes&reverse=true");
    const QString topUrl = QStringLiteral("http://example.com/toponly");

    writeStationCache(topEndpoint, QStringLiteral("Top Only"), topUrl, QStringLiteral("uuid-top"));
    writeStationCache(deEndpoint, QStringLiteral("DE Only"), QStringLiteral("http://example.com/deonly"),
                      QStringLiteral("uuid-de"));

    RadioBackend backend;
    backend.loadTopStations();
    QTRY_VERIFY_WITH_TIMEOUT(backend.stations().size() == 1, 2000);
    backend.playStation(0);
    QCOMPARE(backend.currentStationUrl(), topUrl);
    QVERIFY(!backend.hasNextStation());

    backend.loadGermanStations();
    QTRY_VERIFY_WITH_TIMEOUT(backend.stations().size() == 1, 2000);
    QCOMPARE(backend.currentStationUrl(), topUrl);
    QVERIFY(!backend.hasNextStation());
    QVERIFY(!backend.hasPreviousStation());

    backend.playNextStation();
    QCOMPARE(backend.currentStationUrl(), topUrl);
}

void RadioBackendPlaybackTest::manual_station_rejects_unsafe_url_scheme()
{
    RadioBackend backend;

    backend.addManualStation(QStringLiteral("Unsafe"), QStringLiteral("file:///etc/passwd"), QStringLiteral("DE"));
    QCOMPARE(backend.stations().size(), 0);
    QVERIFY(!backend.lastError().isEmpty());

    backend.addManualStation(QStringLiteral("FTP"), QStringLiteral("ftp://example.com/stream"), QStringLiteral("DE"));
    QCOMPARE(backend.stations().size(), 0);
}

void RadioBackendPlaybackTest::manual_station_accepts_http_and_https()
{
    RadioBackend backend;

    backend.addManualStation(QStringLiteral("HTTP"), QStringLiteral("http://example.com/stream"), QStringLiteral("DE"));
    QCOMPARE(backend.stations().size(), 1);
    QCOMPARE(backend.lastError(), QString());

    backend.addManualStation(QStringLiteral("HTTPS"), QStringLiteral("https://example.com/stream"), QStringLiteral("DE"));
    QCOMPARE(backend.stations().size(), 2);
}

int main(int argc, char *argv[])
{
    qputenv("QT_QPA_PLATFORM", "offscreen");
    qputenv("QT_MEDIA_BACKEND", "ffmpeg");

    QApplication app(argc, argv);
    RadioBackendPlaybackTest test;
    return QTest::qExec(&test, argc, argv);
}

#include "radiobackend_playback_test.moc"