/* WebClientTest.cpp
 *
 * Copyright (C) 2011-2024 Michael Lugmair
 *
 * This file is part of sayonara player
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "test/Common/SayonaraTest.h"

#include "Utils/Utils.h"
#include "Utils/WebAccess/WebClientImpl.h"

#include <QTcpSocket>
#include <QTcpServer>
#include <QByteArray>
#include <QRegExp>
#include <QSignalSpy>

// access working directory with Test::Base::tempPath("somefile.txt");

namespace
{
	constexpr const auto Port = 22444U;

	constexpr const auto EndpointSuccess = "success";
	constexpr const auto EndpointRedirect = "redirect";
	constexpr const auto EndpointStream = "stream";
	constexpr const auto EndpointTimeout = "timeout";
	constexpr const auto EndpointNoData = "nodata";
	constexpr const auto EndpointNoHttpData = "nohttp";

	QString createUrl(const QString& endpoint)
	{
		return QString("http://127.0.0.1:%1/%2").arg(Port).arg(endpoint);
	}

	QByteArray createResponse(int httpStatusCode, const QString& httpStatusString, const QStringList& headerData,
	                          const QString& data = QString())
	{
		const auto header = QString("HTTP/1.1 %1 %2")
		                    .arg(httpStatusCode)
		                    .arg(httpStatusString);

		auto response = QStringList();
		response += header;
		response += QString("Content-Length: %1").arg(data.size());
		response += headerData;
		response += QString();
		response += data;

		return response.join("\r\n").toLocal8Bit();
	}

	void sendResponse(QTcpSocket* socket, int httpStatusCode, const QString& httpStatusString,
	                  const QStringList& headerData,
	                  const QString& data = QString())
	{
		const auto response = createResponse(httpStatusCode, httpStatusString, headerData, data);

		// NOLINTNEXTLINE(readability-magic-numbers)
		Util::sleepMs(Util::randomNumber(200, 1000));
		socket->write(response);
		socket->flush();
	}

	QByteArray parseRequest(const QByteArray& request)
	{
		QRegExp re("GET /?([a-zA-Z0-9]+).*");
		return (re.indexIn(request) >= 0) ? re.cap(1).toLocal8Bit() : QByteArray();
	}
}

class WebServer :
	public QObject
{
		Q_OBJECT

	public:
		explicit WebServer(QObject* parent) :
			mServer{std::make_shared<QTcpServer>(parent)}
		{
			connect(mServer.get(), &QTcpServer::newConnection,
			        this, &WebServer::newConnection);
			mServer->listen(QHostAddress::AnyIPv4, Port);
		}

		~WebServer() override
		{
			mServer->close();
			mServer->deleteLater();
		}

	private slots:
		void socketDataAvailable()
		{
			constexpr const auto ResponseOk = 200;
			constexpr const auto ResponseRedirect = 301;
			constexpr const auto ResponseNotFound = 404;

			auto* socket = dynamic_cast<QTcpSocket*>(sender());

			const auto request = socket->readAll();
			const auto data = parseRequest(request);

			if(data == EndpointSuccess)
			{
				sendResponse(socket, ResponseOk, "OK",
				             {
					             "Content-Type: text/html",
					             "Connection: Closed"
				             },
				             "success");
				return;
			}

			if(data == EndpointRedirect)
			{
				const auto header = QString("Location: /%1").arg(EndpointSuccess);
				sendResponse(socket, ResponseRedirect, "Moved Permanently", {header});
				return;
			}

			if(data == EndpointStream)
			{
				constexpr const auto StringLength = 1000;
				sendResponse(socket, ResponseOk, "OK",
				             {"Content-Type: audio/mpeg", "Connection: keep-alive"},
				             Util::randomString(StringLength));
				return;
			}

			if(data == EndpointNoData)
			{
				sendResponse(socket, ResponseOk, "OK", QStringList());
				return;
			}

			if(data == EndpointTimeout)
			{
				return;
			}

			if(data == EndpointNoHttpData)
			{
				const auto icyData = QString("ICY/1.1 200 OK");

				socket->write(icyData.toLocal8Bit());
				socket->flush();
				return;
			}

			sendResponse(socket, ResponseNotFound, "Not Found", {
				             "Content-Type: text/html",
				             "Connection: close"
			             });
		}

		void newConnection()
		{
			if(const auto* socket = mServer->nextPendingConnection(); socket)
			{
				connect(socket, &QTcpSocket::readyRead, this, &WebServer::socketDataAvailable);
			}
		}

	private: // NOLINT(readability-redundant-access-specifiers)
		std::shared_ptr<QTcpServer> mServer;
};

class WebClientTest :
	public Test::Base
{
		Q_OBJECT

	public:
		WebClientTest() :
			Base("WebClientTest") {}

	private slots:
		[[maybe_unused]] void testSuccess()
		{
			WebServer webserver(this);
			auto* webAccess = new WebClientImpl(this);

			QSignalSpy spy(webAccess, &WebClient::sigFinished);
			webAccess->run(createUrl(EndpointSuccess));
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data() == "success");
			QVERIFY(webAccess->hasData());
			QVERIFY(!webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::GotData);
		}

		[[maybe_unused]] void testNotFound()
		{
			WebServer webserver(this);
			auto* webAccess = new WebClientImpl(this);

			QSignalSpy spy(webAccess, &WebClient::sigFinished);
			webAccess->run(createUrl("foobar"));
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data().isEmpty());
			QVERIFY(!webAccess->hasData());
			QVERIFY(webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::NotFound);
		}


		[[maybe_unused]] void testStream()
		{
			WebServer webserver(this);
			auto* webAccess = new WebClientImpl(this);

			QSignalSpy spy(webAccess, &WebClient::sigFinished);
			webAccess->run(createUrl(EndpointStream));
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data().isEmpty());
			QVERIFY(!webAccess->hasData());
			QVERIFY(!webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::AudioStream);
		}

		[[maybe_unused]] void testNoData()
		{
			WebServer webserver(this);
			auto webAccess = std::make_shared<WebClientImpl>(this);

			QSignalSpy spy(webAccess.get(), &WebClient::sigFinished);
			webAccess->run(createUrl(EndpointNoData));
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data().isEmpty());
			QVERIFY(!webAccess->hasData());
			QVERIFY(!webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::NoData);
		}

		[[maybe_unused]] void testNoHttpData()
		{
			WebServer webserver(this);
			auto webAccess = std::make_shared<WebClientImpl>(this);

			QSignalSpy spy(webAccess.get(), &WebClient::sigFinished);
			webAccess->run(createUrl(EndpointNoHttpData));
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data().isEmpty());
			QVERIFY(!webAccess->hasData());
			QVERIFY(!webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::NoHttp);
		}

		[[maybe_unused]] void testTimeout()
		{
			WebServer webserver(this);
			auto* webAccess = new WebClientImpl(this);

			QSignalSpy spy(webAccess, &WebClient::sigFinished);
			webAccess->run(createUrl(EndpointTimeout), 2500); // NOLINT(readability-magic-numbers)
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data().isEmpty());
			QVERIFY(!webAccess->hasData());
			QVERIFY(webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::Timeout);
		}

		[[maybe_unused]] void testRedirect()
		{
			WebServer webserver(this);
			auto* webAccess = new WebClientImpl(this);

			QSignalSpy spy(webAccess, &WebClient::sigFinished);
			webAccess->run(createUrl(EndpointRedirect));
			QVERIFY(spy.wait(5000));

			QVERIFY(webAccess->data() == "success");
			QVERIFY(webAccess->hasData());
			QVERIFY(!webAccess->hasError());
			QVERIFY(webAccess->status() == WebClient::Status::GotData);
		}
};

QTEST_GUILESS_MAIN(WebClientTest)

#include "WebClientTest.moc"
