#ifndef QT_GUI_LIB
#define QT_GUI_LIB
#endif

#include <QLocale>
#include <QObject>
#include <QRandomGenerator64>
#include <QTest>

#include <ctime>
#include <interfaces/queuemanagerinterface.h>
#include <testlib/dummytorrentcreator.h>
#include <torrent/torrentcontrol.h>
#include <torrent/torrentfilestream.h>
#include <util/error.h>
#include <util/fileops.h>
#include <util/log.h>
#include <util/sha1hashgen.h>

using namespace bt;
using namespace Qt::Literals::StringLiterals;

const bt::Uint32 TEST_FILE_SIZE = 5 * 1024 * 1024;

bt::Uint64 RandomSize(bt::Uint64 min_size, bt::Uint64 max_size)
{
    const bt::Uint64 r = max_size - min_size;
    return min_size + QRandomGenerator64::global()->generate() % r;
}

class TorrentFileStreamMultiTest : public QEventLoop, public bt::QueueManagerInterface
{
    Q_OBJECT
public:
    TorrentFileStreamMultiTest(QObject *parent = nullptr)
        : QEventLoop(parent)
    {
    }

    [[nodiscard]] bool alreadyLoaded(const bt::SHA1Hash &ih) const override
    {
        Q_UNUSED(ih);
        return false;
    }

    void mergeAnnounceList(const bt::SHA1Hash &ih, const bt::TrackerTier *trk) override
    {
        Q_UNUSED(ih);
        Q_UNUSED(trk);
    }

private Q_SLOTS:
    void initTestCase()
    {
        QLocale::setDefault(QLocale(u"main"_s));
        bt::InitLog(u"torrentfilestreammultitest.log"_s, false, false);

        files[u"aaa.avi"_s] = RandomSize(TEST_FILE_SIZE / 2, TEST_FILE_SIZE);
        files[u"bbb.avi"_s] = RandomSize(TEST_FILE_SIZE / 2, TEST_FILE_SIZE);
        files[u"ccc.avi"_s] = RandomSize(TEST_FILE_SIZE / 2, TEST_FILE_SIZE);

        QVERIFY(creator.createMultiFileTorrent(files, u"movies"_s));

        Out(SYS_GEN | LOG_DEBUG) << "Created " << creator.torrentPath() << endl;
        try {
            tc.init(this, bt::LoadFile(creator.torrentPath()), creator.tempPath() + "tor0"_L1, creator.tempPath() + "data/"_L1);
            tc.createFiles();
            QVERIFY(tc.hasExistingFiles());
            tc.startDataCheck(false, 0, tc.getStats().total_chunks);
            do {
                processEvents(AllEvents, 1000);
            } while (tc.getStats().status == bt::CHECKING_DATA);
            QVERIFY(tc.getStats().completed);
        } catch (bt::Error &err) {
            Out(SYS_GEN | LOG_DEBUG) << "Failed to load torrent: " << creator.torrentPath() << endl;
            QFAIL("Torrent load failure");
        }
    }

    void cleanupTestCase()
    {
    }

    void testSimple()
    {
        Out(SYS_GEN | LOG_DEBUG) << "Begin: testSimple() " << endl;
        // Simple test read each file and check if they are the same on disk
        for (bt::Uint32 i = 0; i < tc.getNumFiles(); i++) {
            Out(SYS_GEN | LOG_DEBUG) << "Doing file " << i << endl;
            const bt::TorrentFileStream::Ptr stream = tc.createTorrentFileStream(i, false, this);
            QVERIFY(stream);
            QVERIFY(!stream->open(QIODevice::ReadWrite));
            QVERIFY(stream->open(QIODevice::ReadOnly));
            QCOMPARE(stream->size(), (qint64)tc.getTorrentFile(i).getSize());

            QByteArray tmp(stream->size(), 0);
            qint64 written = 0;
            while (written < stream->size()) {
                const qint64 ret = stream->read(tmp.data(), stream->size());
                Out(SYS_GEN | LOG_DEBUG) << "Returned " << ret << endl;
                QCOMPARE(ret, stream->size());
                written += ret;

                // Load the file and check if the data is the same
                QFile fptr(stream->path());
                QVERIFY(fptr.open(QIODevice::ReadOnly));
                QByteArray tmp2(stream->size(), 0);
                QCOMPARE(fptr.read(tmp2.data(), stream->size()), stream->size());
                QCOMPARE(tmp, tmp2);
            }

            stream->close();
        }
        Out(SYS_GEN | LOG_DEBUG) << "End: testSimple() " << endl;
    }

    void testSeek()
    {
        Out(SYS_GEN | LOG_DEBUG) << "Begin: testSeek() " << endl;

        // In each file take a couple of random samples and compare them with the actual file
        for (bt::Uint32 i = 0; i < tc.getNumFiles(); i++) {
            const bt::TorrentFileStream::Ptr stream = tc.createTorrentFileStream(i, false, this);
            QVERIFY(stream);
            QVERIFY(!stream->open(QIODevice::ReadWrite));
            QVERIFY(stream->open(QIODevice::ReadOnly));
            QCOMPARE(stream->size(), (qint64)tc.getTorrentFile(i).getSize());

            QFile fptr(stream->path());
            QVERIFY(fptr.open(QIODevice::ReadOnly));

            for (bt::Uint32 j = 0; j < 10; j++) {
                const qint64 off = QRandomGenerator64::global()->generate() % (stream->size() - 256);
                QVERIFY(stream->seek(off));
                QVERIFY(fptr.seek(off));

                QByteArray sdata(256, 0);
                QCOMPARE(stream->read(sdata.data(), 256), 256);

                QByteArray fdata(256, 0);
                QCOMPARE(fptr.read(fdata.data(), 256), 256);
                QCOMPARE(sdata, fdata);
            }
            stream->close();
        }

        Out(SYS_GEN | LOG_DEBUG) << "End: testSeek() " << endl;
    }

private:
    DummyTorrentCreator creator;
    bt::TorrentControl tc;
    QMap<QString, bt::Uint64> files;
};

QTEST_MAIN(TorrentFileStreamMultiTest)

#include "torrentfilestreammultitest.moc"
