#include <amxmodx>
#include <amxmisc>
#include <zombie_plague_special>

#define PLUGIN "[ZP] Login System"
#define VERSION "1.0"
#define AUTHOR "Alin"

#define ACCOUNTS_FILE "zpsp_configs/zp_accounts.ini"

new g_logged[33]

public plugin_init()
{
	register_plugin(PLUGIN, VERSION, AUTHOR)
	register_clcmd("say", "cmd_say")
	register_clcmd("say_team", "cmd_say")
}

public client_putinserver(id)
{
	g_logged[id] = false
	set_user_info(id, "_zp_logged", "0")
	client_print(id, print_chat, "[LOGIN] Use /register <password> or /login <password>")
}

public client_disconnected(id)
{
	g_logged[id] = false
}

// ----- Command handler -----

public cmd_say(id)
{
	new text[192]
	read_args(text, charsmax(text))
	remove_quotes(text)
	trim(text)

	if(text[0] != '/')
		return PLUGIN_CONTINUE

	new cmd[32], arg[64]
	parse(text, cmd, charsmax(cmd), arg, charsmax(arg))

	if(equal(cmd, "/register"))
		return cmd_register(id, arg)

	if(equal(cmd, "/login"))
		return cmd_login(id, arg)

	if(equal(cmd, "/logout"))
		return cmd_logout(id)

	if(equal(cmd, "/changename"))
		return cmd_changename(id, arg)

	return PLUGIN_CONTINUE
}

// ----- Commands -----

cmd_register(id, const pass[])
{
	if(strlen(pass) < 3)
	{
		client_print(id, print_chat, "[LOGIN] Password must be at least 3 characters.")
		return PLUGIN_HANDLED
	}

	new name[32]
	get_user_name(id, name, charsmax(name))

	if(load_account(name))
	{
		client_print(id, print_chat, "[LOGIN] This name is already registered.")
		return PLUGIN_HANDLED
	}

	new salt[16], hash[65]
	generate_salt(salt, charsmax(salt))
	hash_password(pass, salt, hash, charsmax(hash))

	if(!save_account(name, salt, hash))
	{
		client_print(id, print_chat, "[LOGIN] Account save failed. Please try again.")
		return PLUGIN_HANDLED
	}

	g_logged[id] = true
	set_user_info(id, "_zp_logged", "1")
	client_print(id, print_chat, "[LOGIN] Registered and logged in.")
	return PLUGIN_HANDLED
}

cmd_login(id, const pass[])
{
	new name[32]
	get_user_name(id, name, charsmax(name))

	if(!load_account(name))
	{
		client_print(id, print_chat, "[LOGIN] This name is not registered. Use /register <password>")
		return PLUGIN_HANDLED
	}

	new stored_salt[16], stored_hash[65]
	if(!load_account_hash(name, stored_salt, charsmax(stored_salt), stored_hash, charsmax(stored_hash)))
	{
		client_print(id, print_chat, "[LOGIN] Account error. Contact admin.")
		return PLUGIN_HANDLED
	}

	new hash[65]
	hash_password(pass, stored_salt, hash, charsmax(hash))

	if(!equal(hash, stored_hash))
	{
		client_print(id, print_chat, "[LOGIN] Wrong password.")
		return PLUGIN_HANDLED
	}

	g_logged[id] = true
	set_user_info(id, "_zp_logged", "1")
	client_print(id, print_chat, "[LOGIN] Logged in successfully.")
	return PLUGIN_HANDLED
}

cmd_logout(id)
{
	if(!g_logged[id])
	{
		client_print(id, print_chat, "[LOGIN] You are not logged in.")
		return PLUGIN_HANDLED
	}

	g_logged[id] = false
	set_user_info(id, "_zp_logged", "0")
	client_print(id, print_chat, "[LOGIN] Logged out.")
	return PLUGIN_HANDLED
}

cmd_changename(id, const newname[])
{
	if(!g_logged[id])
	{
		client_print(id, print_chat, "[LOGIN] You must be logged in to change your name.")
		return PLUGIN_HANDLED
	}

	if(strlen(newname) < 1)
	{
		client_print(id, print_chat, "[LOGIN] Usage: /changename <newname>")
		return PLUGIN_HANDLED
	}

	if(load_account(newname))
	{
		client_print(id, print_chat, "[LOGIN] That name is already registered.")
		return PLUGIN_HANDLED
	}

	new oldname[32]
	get_user_name(id, oldname, charsmax(oldname))

	if(!rename_account(oldname, newname))
	{
		client_print(id, print_chat, "[LOGIN] Failed to rename account.")
		return PLUGIN_HANDLED
	}

	set_user_info(id, "_zp_logged", "1")
	set_user_info(id, "name", newname)
	client_print(id, print_chat, "[LOGIN] Name changed to %s.", newname)
	return PLUGIN_HANDLED
}

// ----- Account file functions -----

load_account(const name[])
{
	new path[256]
	get_configsdir(path, charsmax(path))
	add(path, charsmax(path), "/")
	add(path, charsmax(path), ACCOUNTS_FILE)

	if(!file_exists(path))
		return 0

	new file = fopen(path, "r")
	if(!file)
		return 0

	new line[512], fname[64], fsalt[16], fhash[65]

	while(!feof(file))
	{
		fgets(file, line, charsmax(line))
		trim(line)

		if(line[0] == EOS || line[0] == ';' || line[0] == '/')
			continue

		parse_quoted_line(line, fname, charsmax(fname), fsalt, charsmax(fsalt), fhash, charsmax(fhash))

		if(equali(fname, name))
		{
			fclose(file)
			return 1
		}
	}

	fclose(file)
	return 0
}

load_account_hash(const name[], salt[], slen, hash[], hlen)
{
	new path[256]
	get_configsdir(path, charsmax(path))
	add(path, charsmax(path), "/")
	add(path, charsmax(path), ACCOUNTS_FILE)

	if(!file_exists(path))
		return 0

	new file = fopen(path, "r")
	if(!file)
		return 0

	new line[512], fname[64], fsalt[16], fhash[65]

	while(!feof(file))
	{
		fgets(file, line, charsmax(line))
		trim(line)

		if(line[0] == EOS || line[0] == ';' || line[0] == '/')
			continue

		parse_quoted_line(line, fname, charsmax(fname), fsalt, charsmax(fsalt), fhash, charsmax(fhash))

		if(equali(fname, name))
		{
			copy(salt, slen, fsalt)
			copy(hash, hlen, fhash)
			fclose(file)
			return 1
		}
	}

	fclose(file)
	return 0
}

save_account(const name[], const salt[], const hash[])
{
	new dir[256]
	get_configsdir(dir, charsmax(dir))
	add(dir, charsmax(dir), "/zpsp_configs")

	if(!dir_exists(dir))
		mkdir(dir)

	new path[256]
	get_configsdir(path, charsmax(path))
	add(path, charsmax(path), "/")
	add(path, charsmax(path), ACCOUNTS_FILE)

	new line[512]
	formatex(line, charsmax(line), "^"%s^" ^"%s^" ^"%s^"", name, salt, hash)

	if(write_file(path, line) < 0)
		return 0

	return 1
}

rename_account(const oldname[], const newname[])
{
	new path[256]
	get_configsdir(path, charsmax(path))
	add(path, charsmax(path), "/")
	add(path, charsmax(path), ACCOUNTS_FILE)

	if(!file_exists(path))
		return 0

	new tmppath[256]
	copy(tmppath, charsmax(tmppath), path)
	add(tmppath, charsmax(tmppath), ".tmp")

	new file = fopen(path, "r")
	if(!file)
		return 0

	new tmpfile = fopen(tmppath, "w")
	if(!tmpfile)
	{
		fclose(file)
		return 0
	}

	new line[512], fname[64], fsalt[16], fhash[65]
	new found = 0

	while(!feof(file))
	{
		fgets(file, line, charsmax(line))
		trim(line)

		if(line[0] == EOS)
		{
			fprintf(tmpfile, "^n")
			continue
		}

		if(line[0] == ';' || line[0] == '/')
		{
			fprintf(tmpfile, "%s^n", line)
			continue
		}

		parse_quoted_line(line, fname, charsmax(fname), fsalt, charsmax(fsalt), fhash, charsmax(fhash))

		if(equali(fname, oldname))
		{
			formatex(line, charsmax(line), "^"%s^" ^"%s^" ^"%s^"", newname, fsalt, fhash)
			found = 1
		}

		fprintf(tmpfile, "%s^n", line)
	}

	fclose(file)
	fclose(tmpfile)

	if(!found)
	{
		delete_file(tmppath)
		return 0
	}

	delete_file(path)
	rename_file(tmppath, path)
	return 1
}

// ----- Helpers -----

parse_quoted_line(const line[], name[], namelen, salt[], saltlen, hash[], hashlen)
{
	name[0] = EOS
	salt[0] = EOS
	hash[0] = EOS

	new len = strlen(line)
	new pos = 0
	new tok = 0

	while(pos < len && tok < 3)
	{
		// skip whitespace
		while(pos < len && line[pos] == ' ')
			pos++

		if(pos >= len)
			break

		if(line[pos] == '"')
		{
			pos++ // skip opening quote

			new start = pos
			while(pos < len && line[pos] != '"')
				pos++

			new tlen = pos - start

			switch(tok)
			{
				case 0:
				{
					if(tlen >= namelen)
						tlen = namelen - 1

					if(tlen > 0)
					{
						copy(name, tlen + 1, line[start])
						name[tlen] = EOS
					}
				}
				case 1:
				{
					if(tlen >= saltlen)
						tlen = saltlen - 1

					if(tlen > 0)
					{
						copy(salt, tlen + 1, line[start])
						salt[tlen] = EOS
					}
				}
				case 2:
				{
					if(tlen >= hashlen)
						tlen = hashlen - 1

					if(tlen > 0)
					{
						copy(hash, tlen + 1, line[start])
						hash[tlen] = EOS
					}
				}
			}

			if(pos < len && line[pos] == '"')
				pos++ // skip closing quote

			tok++
		}
		else
		{
			// unquoted token — read to next space (fallback)
			new start = pos
			while(pos < len && line[pos] != ' ')
				pos++

			new tlen = pos - start

			switch(tok)
			{
				case 0:
				{
					if(tlen >= namelen)
						tlen = namelen - 1

					if(tlen > 0)
					{
						copy(name, tlen + 1, line[start])
						name[tlen] = EOS
					}
				}
				case 1:
				{
					if(tlen >= saltlen)
						tlen = saltlen - 1

					if(tlen > 0)
					{
						copy(salt, tlen + 1, line[start])
						salt[tlen] = EOS
					}
				}
				case 2:
				{
					if(tlen >= hashlen)
						tlen = hashlen - 1

					if(tlen > 0)
					{
						copy(hash, tlen + 1, line[start])
						hash[tlen] = EOS
					}
				}
			}

			tok++
		}
	}
}

hash_password(const pass[], const salt[], hash[], hash_len)
{
	new combined[192]
	formatex(combined, charsmax(combined), "%s%s", pass, salt)
	hash_string(combined, Hash_Sha256, hash, hash_len)
}

generate_salt(output[], len)
{
	new chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	for(new i = 0; i < len - 1; i++)
		output[i] = chars[random_num(0, sizeof(chars) - 2)]

	output[len - 1] = 0
}
