/*
 * 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/PlayManagerMock.h"
#include "Playlist/PlaylistTestUtils.h"

#include "Components/Playlist/LocalPathProcessor.h"
#include "Components/Playlist/Playlist.h"
#include "Components/Playlist/PlaylistInterface.h"
#include "Components/Playlist/PlaylistModifiers.h"
#include "Utils/Algorithm.h"
#include "Utils/MetaData/MetaDataList.h"
#include "Utils/Parser/M3UParser.h"
#include "Utils/Settings/Settings.h"
#include "Utils/FileSystem.h"

#include <QStringList>
#include <QSignalSpy>

#include <functional> // bad_function_call

namespace
{
	class PlaylistCreatorMock :
		public Playlist::Creator
	{
		public:
			PlaylistCreatorMock(const Test::Playlist::PathTrackMap& pathTrackMap, const int trackCount) :
				m_playManager {Test::PlayManagerMock {}},
				m_playlist {
					std::make_shared<Playlist::Playlist>(0, "Playlist", &m_playManager, Util::FileSystem::create())
				}
			{
				auto tracks = MetaDataList {};
				for(auto i = 0; i < trackCount; i++)
				{
					tracks << pathTrackMap[i].second;
				}

				Playlist::appendTracks(*m_playlist, tracks, Playlist::Reason::Undefined);
			}

			~PlaylistCreatorMock() override = default;

			PlaylistPtr playlist(int /*playlistIndex*/) override { return m_playlist; }

			PlaylistPtr playlistById(int /*playlistId*/) override { throw std::bad_function_call {}; }

			[[nodiscard]] QString
			requestNewPlaylistName(const QString& /*prefix*/) const override { throw std::bad_function_call {}; }

			int createPlaylist(const MetaDataList& /*tracks*/, const QString& /*name*/,
			                   bool /*temporary*/, bool /*isLocked*/) override { throw std::bad_function_call {}; }

			int createPlaylist(const QStringList& /*pathList*/, const QString& /*name*/,
			                   bool /*temporary*/, Playlist::LocalPathPlaylistCreator* /*creator*/) override
			{
				throw std::bad_function_call {};
			}

			int createPlaylist(const CustomPlaylist& /*customPlaylist*/) override { throw std::bad_function_call {}; }

			int createEmptyPlaylist(bool /*override*/) override { throw std::bad_function_call {}; }

			int createCommandLinePlaylist(const QStringList& /*pathList*/,
			                              Playlist::LocalPathPlaylistCreator* /*creator*/) override
			{
				throw std::bad_function_call {};
			}

		private:
			Test::PlayManagerMock m_playManager;
			PlaylistPtr m_playlist;
	};

	bool checkPlayList(const PlaylistPtr& playlist, const QStringList& paths)
	{
		if(::Playlist::count(*playlist) != paths.count())
		{
			return false;
		}

		const auto& tracks = playlist->tracks();
		for(auto i = 0; i < tracks.count(); i++)
		{
			const auto& track = tracks[i];
			if(track.filepath() != paths[i])
			{
				return false;
			}
		}

		return true;
	}
}

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

class LocalPathProcessorTest :
	public Test::Base
{
	Q_OBJECT

	public:
		LocalPathProcessorTest() :
			Test::Base("ExternTracksPlaylistGeneratorTest"),
			m_pathTrackMap {Test::Playlist::createTrackFiles(Test::Base::tempPath())} {}

	private slots:
		[[maybe_unused]] void testInsertFiles();
		[[maybe_unused]] void testAddFiles();
		[[maybe_unused]] void testAddFilesWithAppend();
		[[maybe_unused]] void testAddFilesWithSingleDir();
		[[maybe_unused]] void testWithPlaylistFile();

	private: // NOLINT(readability-redundant-access-specifiers)
		static void wait(Playlist::LocalPathProcessor* generator);
		const Test::Playlist::PathTrackMap m_pathTrackMap;
};

[[maybe_unused]] void LocalPathProcessorTest::testInsertFiles()
{
	constexpr const auto TrackCount = 5;

	auto* playlistCreator = new PlaylistCreatorMock(m_pathTrackMap, TrackCount);
	auto playlist = playlistCreator->playlist(0);
	auto pathList = QStringList {};

	for(auto i = TrackCount; i < m_pathTrackMap.count(); i++)
	{
		pathList << m_pathTrackMap[i].first;
	}

	auto externTracksPlaylistGenerator = Playlist::LocalPathProcessor(playlist);
	externTracksPlaylistGenerator.insertPaths(pathList, 2);

	wait(&externTracksPlaylistGenerator);

	const auto expectedPaths = QStringList()
		<< m_pathTrackMap[0].first
		<< m_pathTrackMap[1].first
		<< pathList
		<< m_pathTrackMap[2].first
		<< m_pathTrackMap[3].first
		<< m_pathTrackMap[4].first;

	QVERIFY(checkPlayList(playlist, expectedPaths));
}

[[maybe_unused]] void LocalPathProcessorTest::testAddFiles()
{
	auto* playlistCreator = new PlaylistCreatorMock(m_pathTrackMap, 0);
	auto playlist = playlistCreator->playlist(0);
	auto pathList = QStringList {};

	for(const auto& [filepath, metadata]: m_pathTrackMap)
	{
		pathList << filepath;
	}

	auto externTracksPlaylistGenerator = Playlist::LocalPathProcessor(playlist);
	externTracksPlaylistGenerator.addPaths(pathList);

	wait(&externTracksPlaylistGenerator);

	QVERIFY(checkPlayList(playlist, pathList));
}

[[maybe_unused]] void LocalPathProcessorTest::testAddFilesWithAppend()
{
	auto* playlistCreator = new PlaylistCreatorMock(m_pathTrackMap, 3);
	auto playlist = playlistCreator->playlist(0);
	auto pathList = QStringList {};

	Playlist::Mode mode;
	mode.setAppend(Playlist::Mode::On);
	playlist->setMode(mode);

	for(const auto& [filepath, metadata]: m_pathTrackMap)
	{
		pathList << filepath;
	}

	auto externTracksPlaylistGenerator = Playlist::LocalPathProcessor(playlist);
	externTracksPlaylistGenerator.addPaths(pathList);

	wait(&externTracksPlaylistGenerator);

	const auto expectedPaths = QStringList()
		<< m_pathTrackMap[0].first
		<< m_pathTrackMap[1].first
		<< m_pathTrackMap[2].first
		<< pathList;

	QVERIFY(checkPlayList(playlist, expectedPaths));
}

[[maybe_unused]] void LocalPathProcessorTest::testAddFilesWithSingleDir()
{
	auto* playlistCreator = new PlaylistCreatorMock(m_pathTrackMap, 3);
	auto playlist = playlistCreator->playlist(0);
	auto pathList = QStringList {};

	for(const auto& [filepath, metadata]: m_pathTrackMap)
	{
		pathList << filepath;
	}

	pathList.sort();

	auto externTracksPlaylistGenerator = Playlist::LocalPathProcessor(playlist);
	externTracksPlaylistGenerator.addPaths({Test::Base::tempPath()});

	wait(&externTracksPlaylistGenerator);
	auto playlistFiles = QStringList {};
	Util::Algorithm::transform(playlist->tracks(), playlistFiles, [](const auto& track) {
		return track.filepath();
	});

	playlistFiles.sort();

	QVERIFY(pathList == playlistFiles);
}

[[maybe_unused]] void LocalPathProcessorTest::testWithPlaylistFile()
{
	auto* playlistCreator = new PlaylistCreatorMock(m_pathTrackMap, 3);
	auto playlist = playlistCreator->playlist(0);
	auto pathList = QStringList {};
	auto tracks = MetaDataList {};

	for(const auto& [filepath, track]: m_pathTrackMap)
	{
		pathList << filepath;
		tracks << track;
	}

	Util::Algorithm::sort(tracks, [](const auto& track1, const auto track2) {
		return (track1.filepath() < track2.filepath());
	});

	const auto playlistFilename = Test::Base::tempPath("bla.m3u");
	M3UParser::saveM3UPlaylist(playlistFilename, tracks, false);

	auto externTracksPlaylistGenerator = Playlist::LocalPathProcessor(playlist);
	externTracksPlaylistGenerator.addPaths({playlistFilename});

	wait(&externTracksPlaylistGenerator);

	pathList.sort();
	QVERIFY(checkPlayList(playlist, pathList));
}

[[maybe_unused]] void LocalPathProcessorTest::wait(Playlist::LocalPathProcessor* generator)
{
	auto spy = QSignalSpy(generator, &Playlist::LocalPathProcessor::sigFinished);

	QVERIFY(spy.wait(1000));
	QCOMPARE(spy.count(), 1);
}

QTEST_GUILESS_MAIN(LocalPathProcessorTest)

#include "LocalPathProcessorTest.moc"
