/*
 * 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 "Common/SayonaraTest.h"
#include "Common/TestWebClientFactory.h"
#include "Common/PlayManagerMock.h"
#include "Common/NotificationHandlerMock.h"

#include "Components/Streaming/LastFM/LastFM.h"
#include "Components/Notification/NotificationHandler.h"

#include "Utils/MetaData/MetaData.h"
#include "Utils/Algorithm.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Utils.h"

#include <QSignalSpy>
#include <QMap>

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

namespace
{
	QByteArray successfulLoginData();
	QByteArray failedLoginData();
	QByteArray okStatus();

	class LastFmPlayManager : // NOLINT(*-missing-qobject-macro)
		public Test::PlayManagerMock
	{
		public:
			void emitNewTrack(const MetaData& track)
			{
				Test::PlayManagerMock::changeCurrentTrack(track, 0);
				emit sigCurrentTrackChanged(track);
			}
	};

	Test::WebClient* findClient(const QString& searchString, const QList<Test::WebClient*>& clients)
	{
		for(auto* client: clients)
		{
			const auto postData = client->sentPostData();
			if(postData.contains(searchString.toLocal8Bit()))
			{
				return client;
			}
		}

		return nullptr;
	}

	MetaData createSomeTrack()
	{
		auto track = MetaData("/path/to/somewhere.mp3");
		track.setArtist("artist");
		track.setTitle("title");
		return track;
	}
}

class ScrobbleTest :
	public Test::Base
{
	Q_OBJECT

	public:
		ScrobbleTest() :
			Test::Base("ScrobbleTest")
		{
			SetSetting(Set::LFM_ScrobbleTimeSec, 1);
		}

	private:
		Test::NotificationHandlerMock m_notificationHandler;
		LastFmPlayManager m_playManager;

	private slots: // NOLINT(*-redundant-access-specifiers)
		// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
		[[maybe_unused]] void test()
		{
			QCOMPARE(GetSetting(Set::LFM_Active), false);
		}

		// NOLINTNEXTLINE(readability-convert-member-functions-to-static, *-function-cognitive-complexity)
		[[maybe_unused]] void testSuccessfulLogin()
		{
			struct TestCase
			{
				bool lfmActive;
				QString username;
				QString password;
				QByteArray data;
				int expectedClientCount;
				bool expectedSuccess;
			};

			const auto testCases = std::array {
				TestCase {false, "username", "password", {}, 0, false},
				TestCase {false, "username", "password", successfulLoginData(), 0, false},
				TestCase {true, "", "password", successfulLoginData(), 0, false},
				TestCase {true, "username", "", successfulLoginData(), 0, false},
				TestCase {true, "username", "password", failedLoginData(), 1, false},
				TestCase {true, "username", "password", successfulLoginData(), 1, true},
			};

			for(const auto& testCase: testCases)
			{
				SetSetting(Set::LFM_Active, testCase.lfmActive);
				auto webClientFactory = std::make_shared<Test::WebClientFactory>();
				auto lastFm = LastFM::Base(&m_playManager, &m_notificationHandler, webClientFactory);
				auto spy = QSignalSpy(&lastFm, &LastFM::Base::sigLoggedIn);

				QVERIFY(!lastFm.isLoggedIn());

				lastFm.login(testCase.username, testCase.password);

				const auto clients = webClientFactory->clients();
				QCOMPARE(clients.count(), testCase.expectedClientCount);
				if(testCase.expectedClientCount > 0)
				{
					clients[0]->fireData(testCase.data, WebClient::Status::GotData);
					QCOMPARE(spy.count(), 1);
					QCOMPARE(lastFm.isLoggedIn(), testCase.expectedSuccess);
				}
			}
		}

		// NOLINTNEXTLINE(readability-convert-member-functions-to-static, *-function-cognitive-complexity)
		[[maybe_unused]] void testLastFmScrobblesAndUpdatesAfterTrackChange()
		{
			SetSetting(Set::LFM_Active, true);

			auto webClientFactory = std::make_shared<Test::WebClientFactory>();
			auto lastFm = LastFM::Base(&m_playManager, &m_notificationHandler, webClientFactory);
			lastFm.login("user", "password");

			auto clients = webClientFactory->clients();
			clients[0]->fireData(successfulLoginData(), WebClient::Status::GotData);
			QCOMPARE(lastFm.isLoggedIn(), true);

			m_playManager.emitNewTrack(createSomeTrack());

			constexpr const auto methods = std::array {"track.scrobble", "track.updatenowplaying"};
			for(const auto& method: methods)
			{
				Test::WebClient* client = nullptr;
				const auto clientFound = QTest::qWaitFor([&]() {
					client = findClient(method, webClientFactory->clients());
					return client != nullptr;
				}, 1500);

				QVERIFY(clientFound);

				client->fireData(okStatus(), WebClient::Status::GotData);
				QVERIFY(!client->hasError());
			}
		}
};

namespace
{
	QByteArray successfulLoginData()
	{
		return R"(
<lfm status="ok">
	<name>Luke</name>
	<key>0bee89b07a248e27c83fc3d5951213c1</key>
</lfm>)";
	}

	QByteArray failedLoginData()
	{
		return R"(
<lfm status="failed">
	<error code="123">Some error message</error>
</lfm>)";
	}

	QByteArray okStatus()
	{
		return R"(<lfm status="ok"></lfm>)";
	}
}

QTEST_GUILESS_MAIN(ScrobbleTest)

#include "ScrobbleTest.moc"
