/* Fixes.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 "Fixes.h"
#include "Module.h"
#include "Query.h"
#include "Settings.h"
#include "Utils/Logger/Logger.h"

#include "Utils/Utils.h"

#include <QSqlQuery>

namespace DB
{
	namespace
	{
		void dropViews(const QString& prefix, const QString& connectionName)
		{
			auto module = Module{connectionName};
			auto q = module.runQuery("SELECT name FROM sqlite_master "
			                         "WHERE type='view' AND name like :prefix;",
			                         {":prefix", prefix},
			                         "Cannot get album views");
			if (!hasError(q))
			{
				while (q.next())
				{
					auto view = q.value(0).toString();
					module.runQuery(QString("DROP VIEW IF EXISTS %1;").arg(view),
					                QString("Cannot drop view %1").arg(view));
				}
			}
		}
	}

	struct Fixes::Private
	{
		QString connectionName;

		explicit Private(QString connectionName) :
			connectionName{std::move(connectionName)}
		{
		}
	};

	Fixes::Fixes(const QString& connectionName) :
		m{Pimpl::make<Private>(connectionName)}
	{
	}

	Fixes::~Fixes() noexcept = default;

	bool Fixes::checkAndDropTable(const QString& tablename)
	{
		auto module = Module(m->connectionName);
		auto q = module.runQuery(QString("DROP TABLE IF EXISTS %1;").arg(tablename),
		                         QString("Cannot drop table %1").arg(tablename));

		return !hasError(q);
	}

	bool Fixes::checkAndInsertColumn(const QString& tablename, const QString& column, const QString& sqltype)
	{
		return checkAndInsertColumn(tablename, column, sqltype, QString());
	}

	bool Fixes::checkAndInsertColumn(const QString& tablename, const QString& column, const QString& sqltype,
	                                 const QString& defaultValue)
	{
		auto module = Module{m->connectionName};
		const auto querytext = QString("SELECT %1 FROM %2;")
		                       .arg(column)
		                       .arg(tablename);

		auto q = module.runQuery(querytext, QString());
		if (hasError(q))
		{
			auto alterTable = QString("ALTER TABLE %1 ADD COLUMN %2 %3")
			                  .arg(tablename)
			                  .arg(column)
			                  .arg(sqltype);

			if (!defaultValue.isEmpty())
			{
				alterTable += QString(" DEFAULT %1").arg(defaultValue);
			}

			alterTable += ";";

			const auto errorString = QString("Cannot insert column %1 into %2")
			                         .arg(column)
			                         .arg(tablename);

			auto alterTableQuery = module.runQuery(alterTable, errorString);
			return !hasError(alterTableQuery);
		}

		return true;
	}

	bool Fixes::checkAndCreateTable(const QString& tablename, const QString& sql)
	{
		auto module = Module{m->connectionName};

		const auto selectStatement = QString("SELECT * FROM %1;").arg(tablename);
		auto q = module.runQuery(selectStatement, QString());
		if (hasError(q))
		{
			const auto createQuery = module.runQuery(sql, QString("Cannot create table %1").arg(tablename));
			return !hasError(createQuery);
		}

		return true;
	}

	bool Fixes::removeColumn(const QString& tablename, const QString& column)
	{
		auto module = Module{m->connectionName};

		const auto str = QString("ALTER TABLE %1 DROP COLUMN %2;")
		                 .arg(tablename)
		                 .arg(column);

		const auto q = module.runQuery(str, QString("Cannot remove column %1 from %2").arg(column).arg(tablename));
		return !hasError(q);
	}

	bool Fixes::checkIfTableExists(const QString& tablename)
	{
		auto module = Module{m->connectionName};

		auto q = module.runQuery("SELECT name FROM sqlite_master WHERE type='table' AND UPPER(name) = :tablename;",
		                         {":tablename", tablename.toUpper()},
		                         QString("Cannot check if %1 exists").arg(tablename));

		return !hasError(q) && q.next();
	}

	void Fixes::dropAllViews()
	{
		dropViews("album_view%", m->connectionName);
		dropViews("track_view%", m->connectionName);
		dropViews("track_search_view%", m->connectionName);
	}

	bool Fixes::setForeignKeysEnabled(const bool enabled)
	{
		auto module = Module{m->connectionName};
		const auto q = module.runQuery(
			QStringLiteral("PRAGMA foreign_keys = %1;")
			.arg(enabled ? QStringLiteral("ON") : QStringLiteral("OFF")),
			QStringLiteral("Cannot turn off foreign key pragma"));

		return !hasError(q);
	}

	bool Fixes::renameTable(const QString& oldTable, const QString& newTable)
	{
		const auto module = Module{m->connectionName};
		const auto q = module.runQuery(
			QStringLiteral("ALTER TABLE %1 RENAME TO %2")
			.arg(oldTable)
			.arg(newTable),
			QStringLiteral("Cannot rename table %1 to %2")
			.arg(oldTable)
			.arg(newTable));

		return !hasError(q);
	}
} // DB
