/**
 * vim: set ts=4 :
 * =============================================================================
 * SourceMod SQL Admin Manager Plugin
 * Adds/managers admins and groups in an SQL database.
 *
 * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
 * =============================================================================
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 3.0, as published by the
 * Free Software Foundation.
 * 
 * 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/>.
 *
 * As a special exception, AlliedModders LLC gives you permission to link the
 * code of this program (as well as its derivative works) to "Half-Life 2," the
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
 * by the Valve Corporation.  You must obey the GNU General Public License in
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
 * this exception to all derivative works.  AlliedModders LLC defines further
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
 * or <http://www.sourcemod.net/license.php>.
 *
 * Version: $Id$
 */

/* We like semicolons */
#pragma semicolon 1

#include <sourcemod>

public Plugin:myinfo = 
{
	name = "SQL Admin Manager",
	author = "AlliedModders LLC",
	description = "Manages SQL admins",
	version = SOURCEMOD_VERSION,
	url = "http://www.sourcemod.net/"
};

public OnPluginStart()
{
	LoadTranslations("common.phrases");
	LoadTranslations("sqladmins.phrases");

	RegAdminCmd("sm_sql_addadmin", Command_AddAdmin, ADMFLAG_ROOT, "Adds an admin to the SQL database");
	RegAdminCmd("sm_sql_deladmin", Command_DelAdmin, ADMFLAG_ROOT, "Removes an admin from the SQL database");
	RegAdminCmd("sm_sql_addgroup", Command_AddGroup, ADMFLAG_ROOT, "Adds a group to the SQL database");
	RegAdminCmd("sm_sql_delgroup", Command_DelGroup, ADMFLAG_ROOT, "Removes a group from the SQL database");
	RegAdminCmd("sm_sql_setadmingroups", Command_SetAdminGroups, ADMFLAG_ROOT, "Sets an admin's groups in the SQL database");
}

Handle:Connect()
{
	decl String:error[255];
	new Handle:db;
	
	if (SQL_CheckConfig("admins"))
	{
		db = SQL_Connect("admins", true, error, sizeof(error));
	} else {
		db = SQL_Connect("default", true, error, sizeof(error));
	}
	
	if (db == INVALID_HANDLE)
	{
		LogError("Could not connect to database: %s", error);
	}
	
	return db;
}

public Action:Command_SetAdminGroups(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "[SM] Usage: sm_sql_setadmingroups <authtype> <identity> [group1] ... [group N]");
		return Plugin_Handled;
	}
	
	decl String:authtype[16];
	GetCmdArg(1, authtype, sizeof(authtype));
	
	if (!StrEqual(authtype, "steam")
		&& !StrEqual(authtype, "ip")
		&& !StrEqual(authtype, "name"))
	{
		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
		return Plugin_Handled;
	}
	
	new Handle:db = Connect();
	if (db == INVALID_HANDLE)
	{
		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
		return Plugin_Handled;
	}
	
	decl String:identity[65];
	decl String:safe_identity[140];
	GetCmdArg(2, identity, sizeof(identity));
	SQL_QuoteString(db, identity, safe_identity, sizeof(safe_identity));
	
	decl String:query[255];
	Format(query, 
		sizeof(query),
		"SELECT id FROM sm_admins WHERE authtype = '%s' AND identity = '%s'",
		authtype,
		safe_identity);
		
	new Handle:hQuery;
	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
	{
		return DoError(client, db, query, "Admin lookup query failed");
	}
	
	if (!SQL_FetchRow(hQuery))
	{
		ReplyToCommand(client, "[SM] %t", "SQL Admin not found");
		CloseHandle(hQuery);
		CloseHandle(db);
		return Plugin_Handled;
	}
	
	new id = SQL_FetchInt(hQuery, 0);
	
	CloseHandle(hQuery);
	
	/**
	 * First delete all of the user's existing groups.
	 */
	Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE admin_id = %d", id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Admin group deletion query failed");
	}
	
	if (args < 3)
	{
		ReplyToCommand(client, "[SM] %t", "SQL Admin groups reset");
		CloseHandle(db);
		return Plugin_Handled;
	}
	
	decl String:error[256];
	new Handle:hAddQuery, Handle:hFindQuery;
	
	Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = ?");
	if ((hFindQuery = SQL_PrepareQuery(db, query, error, sizeof(error))) == INVALID_HANDLE)
	{
		return DoStmtError(client, db, query, error, "Group search prepare failed");
	}
	
	Format(query, 
		sizeof(query), 
		"INSERT INTO sm_admins_groups (admin_id, group_id, inherit_order) VALUES (%d, ?, ?)",
		id);
	if ((hAddQuery = SQL_PrepareQuery(db, query, error, sizeof(error))) == INVALID_HANDLE)
	{
		CloseHandle(hFindQuery);
		return DoStmtError(client, db, query, error, "Add admin group prepare failed");
	}
	
	decl String:name[80];
	new inherit_order = 0;
	for (new i=3; i<=args; i++)
	{
		GetCmdArg(i, name, sizeof(name));
		
		SQL_BindParamString(hFindQuery, 0, name, false);
		if (!SQL_Execute(hFindQuery) || !SQL_FetchRow(hFindQuery))
		{
			ReplyToCommand(client, "[SM] %t", "SQL Group X not found", name);
		} else {
			new gid = SQL_FetchInt(hFindQuery, 0);
			
			SQL_BindParamInt(hAddQuery, 0, gid);
			SQL_BindParamInt(hAddQuery, 1, ++inherit_order);
			if (!SQL_Execute(hAddQuery))
			{
				ReplyToCommand(client, "[SM] %t", "SQL Group X failed to bind", name);
				inherit_order--;
			}
		}
	}
	
	CloseHandle(hAddQuery);
	CloseHandle(hFindQuery);
	CloseHandle(db);
	
	if (inherit_order == 1)
	{
		ReplyToCommand(client, "[SM] %t", "Added group to user");
	} else if (inherit_order > 1) {
		ReplyToCommand(client, "[SM] %t", "Added groups to user", inherit_order);
	}
	
	return Plugin_Handled;
}

public Action:Command_DelGroup(client, args)
{
	if (args < 1)
	{
		ReplyToCommand(client, "[SM] Usage: sm_sql_delgroup <name>");
		return Plugin_Handled;
	}

	new Handle:db = Connect();
	if (db == INVALID_HANDLE)
	{
		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
		return Plugin_Handled;
	}
	
	new len;
	decl String:name[80];
	decl String:safe_name[180];
	GetCmdArgString(name, sizeof(name));
	
	/* Strip quotes in case the user tries to use them */
	len = strlen(name);
	if (len > 1 && (name[0] == '"' && name[len-1] == '"'))
	{
		name[--len] = '\0';
		SQL_QuoteString(db, name[1], safe_name, sizeof(safe_name));
	} else {
		SQL_QuoteString(db, name, safe_name, sizeof(safe_name));
	}
	
	decl String:query[256];
	
	new Handle:hQuery;
	Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = '%s'", safe_name);
	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
	{
		return DoError(client, db, query, "Group retrieval query failed");
	}
	
	if (!SQL_FetchRow(hQuery))
	{
		ReplyToCommand(client, "[SM] %t", "SQL Group not found");
		CloseHandle(hQuery);
		CloseHandle(db);
		return Plugin_Handled;
	}
	
	new id = SQL_FetchInt(hQuery, 0);
	
	CloseHandle(hQuery);
	
	/* Delete admin inheritance for this group */
	Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE group_id = %d", id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Admin group deletion query failed");
	}
	
	/* Delete group overrides */
	Format(query, sizeof(query), "DELETE FROM sm_group_overrides WHERE group_id = %d", id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Group override deletion query failed");
	}
	
	/* Delete immunity */
	Format(query, sizeof(query), "DELETE FROM sm_group_immunity WHERE group_id = %d OR other_id = %d", id, id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Group immunity deletion query failed");
	}
	
	/* Finally delete the group */
	Format(query, sizeof(query), "DELETE FROM sm_groups WHERE id = %d", id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Group deletion query failed");
	}
	
	ReplyToCommand(client, "[SM] %t", "SQL Group deleted");
	
	CloseHandle(db);
	
	return Plugin_Handled;
}

public Action:Command_AddGroup(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "[SM] Usage: sm_sql_addgroup <name> <flags> [immunity]");
		ReplyToCommand(client, "[SM] %t", "Invalid immunity");
		return Plugin_Handled;
	}

	new String:immunity[32] = "none";
	if (args >= 3)
	{
		GetCmdArg(3, immunity, sizeof(immunity));
		if (!StrEqual(immunity, "none")
			&& !StrEqual(immunity, "global")
			&& !StrEqual(immunity, "default"))
		{
			ReplyToCommand(client, "[SM] %t", "Invalid immunity");
			return Plugin_Handled;
		}
	}
	
	new Handle:db = Connect();
	if (db == INVALID_HANDLE)
	{
		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
		return Plugin_Handled;
	}
	
	decl String:name[64];
	decl String:safe_name[64];
	GetCmdArg(1, name, sizeof(name));
	SQL_QuoteString(db, name, safe_name, sizeof(safe_name));
	
	new Handle:hQuery;
	decl String:query[256];
	Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = '%s'", safe_name);
	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
	{
		return DoError(client, db, query, "Group retrieval query failed");
	}
	
	if (SQL_GetRowCount(hQuery) > 0)
	{
		ReplyToCommand(client, "[SM] %t", "SQL Group already exists");
		CloseHandle(hQuery);
		CloseHandle(db);
		return Plugin_Handled;
	}
	
	CloseHandle(hQuery);
	
	decl String:flags[30];
	decl String:safe_flags[64];
	GetCmdArg(2, flags, sizeof(safe_flags));
	SQL_QuoteString(db, flags, safe_flags, sizeof(safe_flags));
	
	Format(query, 
		sizeof(query),
		"INSERT INTO sm_groups (immunity, flags, name) VALUES ('%s', '%s', '%s')",
		immunity,
		safe_flags,
		safe_name);
	
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Group insertion query failed");
	}
	
	ReplyToCommand(client, "[SM] %t", "SQL Group added");
	
	CloseHandle(db);
		
	return Plugin_Handled;
}	

public Action:Command_DelAdmin(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "[SM] Usage: sm_sql_deladmin <authtype> <identity>");
		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
		return Plugin_Handled;
	}
	
	decl String:authtype[16];
	GetCmdArg(1, authtype, sizeof(authtype));
	
	if (!StrEqual(authtype, "steam")
		&& !StrEqual(authtype, "ip")
		&& !StrEqual(authtype, "name"))
	{
		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
		return Plugin_Handled;
	}
	
	new Handle:db = Connect();
	if (db == INVALID_HANDLE)
	{
		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
		return Plugin_Handled;
	}
	
	decl String:identity[65];
	decl String:safe_identity[140];
	GetCmdArg(2, identity, sizeof(identity));
	SQL_QuoteString(db, identity, safe_identity, sizeof(safe_identity));
	
	decl String:query[255];
	Format(query, 
		sizeof(query),
		"SELECT id FROM sm_admins WHERE authtype = '%s' AND identity = '%s'",
		authtype,
		safe_identity);
		
	new Handle:hQuery;
	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
	{
		return DoError(client, db, query, "Admin lookup query failed");
	}
	
	if (!SQL_FetchRow(hQuery))
	{
		ReplyToCommand(client, "[SM] %t", "SQL Admin not found");
		CloseHandle(hQuery);
		CloseHandle(db);
		return Plugin_Handled;
	}
	
	new id = SQL_FetchInt(hQuery, 0);
	
	CloseHandle(hQuery);
	
	/* Delete group bindings */
	Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE admin_id = %d", id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Admin group deletion query failed");
	}
	
	Format(query, sizeof(query), "DELETE FROM sm_admins WHERE id = %d", id);
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Admin deletion query failed");
	}
	
	CloseHandle(db);
	
	ReplyToCommand(client, "[SM] %t", "SQL Admin deleted");
	
	return Plugin_Handled;
}

public Action:Command_AddAdmin(client, args)
{
	if (args < 4)
	{
		ReplyToCommand(client, "[SM] Usage: sm_sql_addadmin <alias> <authtype> <identity> <flags> [password]");
		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
		return Plugin_Handled;
	}
	
	decl String:authtype[16];
	GetCmdArg(2, authtype, sizeof(authtype));
	
	if (!StrEqual(authtype, "steam")
		&& !StrEqual(authtype, "ip")
		&& !StrEqual(authtype, "name"))
	{
		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
		return Plugin_Handled;
	}
	
	decl String:identity[65];
	decl String:safe_identity[140];
	GetCmdArg(3, identity, sizeof(identity));
	
	decl String:query[256];
	new Handle:hQuery;
	new Handle:db = Connect();
	if (db == INVALID_HANDLE)
	{
		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
		return Plugin_Handled;
	}
	
	SQL_QuoteString(db, identity, safe_identity, sizeof(safe_identity));
	
	Format(query, sizeof(query), "SELECT id FROM sm_admins WHERE authtype = '%s' AND identity = '%s'", authtype, identity);
	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
	{
		return DoError(client, db, query, "Admin retrieval query failed");
	}
	
	if (SQL_GetRowCount(hQuery) > 0)
	{
		ReplyToCommand(client, "[SM] %t", "SQL Admin already exists");
		CloseHandle(hQuery);
		CloseHandle(db);
		return Plugin_Handled;
	}
	
	CloseHandle(hQuery);
	
	decl String:alias[64];
	decl String:safe_alias[140];
	GetCmdArg(1, alias, sizeof(alias));
	SQL_QuoteString(db, alias, safe_alias, sizeof(safe_alias));
	
	decl String:flags[30];
	decl String:safe_flags[64];
	GetCmdArg(4, flags, sizeof(flags));
	SQL_QuoteString(db, flags, safe_flags, sizeof(safe_flags));
	
	decl String:password[32];
	decl String:safe_password[80];
	if (args > 4)
	{
		GetCmdArg(5, password, sizeof(password));
		SQL_QuoteString(db, password, safe_password, sizeof(safe_password));
	} else {
		safe_password[0] = '\0';
	}
	
	new len = 0;
	len += Format(query[len], sizeof(query)-len, "INSERT INTO sm_admins (authtype, identity, password, flags, name) VALUES");
	if (safe_password[0] == '\0')
	{
		len += Format(query[len], sizeof(query)-len, " ('%s', '%s', NULL, '%s', '%s')", authtype, safe_identity, safe_flags, safe_alias);
	} else {
		len += Format(query[len], sizeof(query)-len, " ('%s', '%s', '%s', '%s', '%s')", authtype, safe_identity, safe_password, safe_flags, safe_alias);
	}
	
	if (!SQL_FastQuery(db, query))
	{
		return DoError(client, db, query, "Admin insertion query failed");
	}
	
	ReplyToCommand(client, "[SM] %t", "SQL Admin added");
	
	CloseHandle(db);
		
	return Plugin_Handled;
}

stock Action:DoError(client, Handle:db, const String:query[], const String:msg[])
{
		decl String:error[255];
		SQL_GetError(db, error, sizeof(error));
		LogError("%s: %s", msg, error);
		LogError("Query dump: %s", query);
		CloseHandle(db);
		ReplyToCommand(client, "[SM] %t", "Failed to query database");
		return Plugin_Handled;
}

stock Action:DoStmtError(client, Handle:db, const String:query[], const String:error[], const String:msg[])
{
		LogError("%s: %s", msg, error);
		LogError("Query dump: %s", query);
		CloseHandle(db);
		ReplyToCommand(client, "[SM] %t", "Failed to query database");
		return Plugin_Handled;
}