/* DatabaseArtists.cpp */

/* Copyright (C) 2011-2024 Michael Lugmair (Lucio Carreras)
 *
 * 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 "Database/Artists.h"
#include "Database/Module.h"
#include "Database/Utils.h"
#include "Database/Query.h"

#include "Utils/MetaData/MetaData.h"
#include "Utils/MetaData/Artist.h"
#include "Utils/Library/Filter.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Utils.h"

using DB::Artists;

namespace
{
	QString fetchQueryArtists(const QString& trackView, QString whereStatement, const bool onlyAlbumArtistIds,
	                          const bool alsoEmpty)
	{
		static const auto fields = QStringList{
			QStringLiteral("artists.artistId AS artistId"),
			QStringLiteral("artists.name AS artistName"),
			QStringLiteral("COUNT(DISTINCT trackId) AS trackCount")}.join(", ");

		const auto joinType = alsoEmpty ? QStringLiteral("LEFT OUTER") : QStringLiteral("INNER");
		const auto columns = onlyAlbumArtistIds
			? QStringList{QStringLiteral("albumArtistId")}
			: QStringList{QStringLiteral("artistId"), QStringLiteral("albumArtistId")};

		if (whereStatement.isEmpty())
		{
			whereStatement = QStringLiteral("1");
		}

		auto unionStatements = QStringList{};
		for (const auto& column: columns)
		{
			unionStatements << QStringLiteral("SELECT trackId, %1 AS aid FROM %2 WHERE %3")
			                   .arg(column)
			                   .arg(trackView)
			                   .arg(whereStatement);
		}

		const auto combinedTracks = unionStatements.join(QStringLiteral(" UNION "));

		return QStringLiteral(
				"SELECT %1 "
				"FROM artists "
				"%2 JOIN (%3) AS combinedTracks ON combinedTracks.aid = artists.artistId "
				"GROUP BY artists.artistId, artists.name")
		       .arg(fields)
		       .arg(joinType)
		       .arg(combinedTracks);
	}

	ArtistList dbFetchArtists(QSqlQuery& q)
	{
		if (!q.exec())
		{
			DB::showError(q, QStringLiteral("Could not get all artists from database"));
			return {};
		}

		auto result = ArtistList{};

		while (q.next())
		{
			Artist artist;
			artist.setId(q.value(0).value<ArtistId>());
			artist.setName(q.value(1).toString());
			artist.setSongcount(q.value(2).value<uint16_t>());

			result.push_back(std::move(artist));
		}

		return result;
	}

}

Artists::Artists() = default;
Artists::~Artists() = default;

Artist Artists::getArtistByID(const ArtistId id) const { return getArtistByID(id, false); }

Artist Artists::getArtistByID(const ArtistId id, const bool alsoEmpty) const
{
	if (id < 0)
	{
		return {};
	}

	const auto whereStatement = QStringLiteral("artistId = :artistId");
	const auto queryText = fetchQueryArtists(trackView(), whereStatement, false, alsoEmpty);
	auto query = module()->runQuery(queryText, {QStringLiteral("artistId"), id},
	                                QStringLiteral("Cannot fetch artist by ID %1").arg(id));

	const auto artists = dbFetchArtists(query);
	return !artists.empty()
		       ? artists[0]
		       : Artist{};
}

ArtistId Artists::getArtistID(const QString& artist) const
{
	const auto queryText = QStringLiteral("SELECT artistID FROM artists WHERE name = :name;");
	auto query = module()->runQuery(
		queryText,
		{QStringLiteral(":name"), Util::convertNotNull(artist)},
		QStringLiteral("Cannot fetch artistID for artist %1").arg(artist)
	);

	if (hasError(query))
	{
		return -1;
	}

	return query.next()
		       ? query.value(0).toInt()
		       : -1;
}

ArtistList Artists::getAllArtists(const bool onlyAlbumArtists, const bool alsoEmpty) const
{
	const auto queryText = fetchQueryArtists(trackView(), {}, onlyAlbumArtists, alsoEmpty);

	auto query = module()->runQuery(queryText, "Cannot fetch all artists");
	return dbFetchArtists(query);
}

ArtistList Artists::getAllArtistsBySearchString(const bool onlyAlbumArtists, const Library::Filter& filter) const
{
	static const auto cisPlaceholder = QStringLiteral(":cissearch");

	auto result = ArtistList{};

	const auto whereStatement = getFilterWhereStatement(filter, cisPlaceholder);
	const auto queryText = fetchQueryArtists(trackSearchView(), whereStatement, onlyAlbumArtists, false);

	const auto searchFilters = filter.searchModeFiltertext(true, GetSetting(Set::Lib_SearchMode));
	for (const auto& searchFilter: searchFilters)
	{
		auto query = QSqlQuery(module()->db());
		query.prepare(queryText);
		query.bindValue(cisPlaceholder, Util::convertNotNull(searchFilter));

		const auto artists = dbFetchArtists(query);
		result.appendUnique(artists);
	}

	return result;
}

bool Artists::deleteArtist(ArtistId id)
{
	const auto queryText = QStringLiteral("DELETE FROM artists WHERE artistId = :artistId;");
	auto query = module()->runQuery(
		queryText,
		{QStringLiteral(":artistId"), id},
		QStringLiteral("Cannot delete artist %1").arg(id));

	return !hasError(query);
}

ArtistId Artists::insertArtistIntoDatabase(const QString& artist)
{
	if (const auto id = getArtistID(artist); id >= 0)
	{
		return id;
	}

	const auto searchString = Library::convertSearchstring(artist);

	const auto bindings = QMap<QString, QVariant>
	{
		{QStringLiteral("name"), Util::convertNotNull(artist)},
		{QStringLiteral("cissearch"), Util::convertNotNull(searchString)}
	};

	const auto query = module()->insert(QStringLiteral("artists"), bindings,
	                                    QStringLiteral("Cannot insert artist %1").arg(artist));

	return hasError(query)
		       ? -1
		       : query.lastInsertId().toInt();
}

[[maybe_unused]] ArtistId Artists::insertArtistIntoDatabase(const Artist& artist)
{
	return insertArtistIntoDatabase(artist.name());
}

void Artists::updateArtistCissearch()
{
	const auto artists = getAllArtists(false, true);

	module()->db().transaction();

	for (const auto& artist: artists)
	{
		const auto searchString = Library::convertSearchstring(artist.name());

		module()->update(
			QStringLiteral("artists"),
			{
				{QStringLiteral("cissearch"), Util::convertNotNull(searchString)}
			},
			{QStringLiteral("artistID"), artist.id()},
			QStringLiteral("Cannot update artist cissearch"));
	}

	module()->db().commit();
}

void Artists::deleteAllArtists()
{
	module()->runQuery(
		QStringLiteral("DELETE FROM artists;"),
		QStringLiteral("Could not delete all artists"));
}
