// Anope IRC Services <https://www.anope.org/>
//
// Copyright (C) 2003-2026 Anope Contributors
//
// Anope is free software. You can use, modify, and/or distribute it under the
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
// for the complete terms of this license and docs/AUTHORS.txt for a list of
// contributors.
//
// Based on the original code of Epona by Lara
// Based on the original code of Services by Andy Church
//
// SPDX-License-Identifier: GPL-2.0-only

#include "module.h"
#include "modules/botserv/badwords.h"
#include "modules/chanserv/akick.h"

class CommandCSClone final
	: public Command
{
	static void CopySetting(ChannelInfo *ci, ChannelInfo *target_ci, const Anope::string &setting)
	{
		if (ci->HasExt(setting))
			target_ci->Extend<bool>(setting);
	}

	static void CopyAccess(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
	{
		std::set<Anope::string> masks;
		auto access_max = Config->GetModule("chanserv").Get<unsigned>("accessmax", "1000");
		unsigned count = 0;

		for (unsigned i = 0; i < target_ci->GetAccessCount(); ++i)
			masks.insert(target_ci->GetAccess(i)->Mask());

		for (unsigned i = 0; i < ci->GetAccessCount(); ++i)
		{
			const ChanAccess *taccess = ci->GetAccess(i);
			AccessProvider *provider = taccess->provider;

			if (access_max && target_ci->GetDeepAccessCount() >= access_max)
				break;

			if (masks.count(taccess->Mask()))
				continue;
			masks.insert(taccess->Mask());

			ChanAccess *newaccess = provider->Create();
			newaccess->SetMask(taccess->Mask(), target_ci);
			newaccess->creator = taccess->creator;
			newaccess->description = taccess->description;
			newaccess->last_seen = taccess->last_seen;
			newaccess->created = taccess->created;
			newaccess->AccessUnserialize(taccess->AccessSerialize());

			target_ci->AddAccess(newaccess);

			++count;
		}

		source.Reply(_("%d access entries from \002%s\002 have been cloned to \002%s\002."), count, ci->name.c_str(), target_ci->name.c_str());
	}

	static void CopyAkick(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
	{
		if (!ChanServ::akick_service)
		{
			source.Reply(TRY_AGAIN_LATER, source.command.nobreak().c_str());
			return;
		}

		ChanServ::akick_service->ClearAKick(target_ci);
		for (unsigned i = 0; i < ChanServ::akick_service->GetAKickCount(ci); ++i)
		{
			const auto *akick = ChanServ::akick_service->GetAKick(ci, i);
			if (akick->nc)
				ChanServ::akick_service->AddAKick(target_ci, akick->creator, akick->nc, akick->reason, akick->addtime, akick->last_used);
			else
				ChanServ::akick_service->AddAKick(target_ci, akick->creator, akick->mask, akick->reason, akick->addtime, akick->last_used);
		}

		source.Reply(_("All akick entries from \002%s\002 have been cloned to \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
	}

	static void CopyBadwords(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
	{
		auto *target_badwords = target_ci->Require<BotServ::BadWords>(BOTSERV_BAD_WORDS_EXT);
		auto *badwords = ci->Require<BotServ::BadWords>(BOTSERV_BAD_WORDS_EXT);

		if (!target_badwords || !badwords)
		{
			source.Reply(TRY_AGAIN_LATER, source.command.nobreak().c_str());
			return;
		}

		target_badwords->ClearBadWords();

		for (unsigned i = 0; i < badwords->GetBadWordCount(); ++i)
		{
			const auto *bw = badwords->GetBadWord(i);
			target_badwords->AddBadWord(bw->word, bw->type);
		}

		badwords->Check();
		target_badwords->Check();

		source.Reply(_("All badword entries from \002%s\002 have been cloned to \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
	}

	static void CopyLevels(CommandSource &source, ChannelInfo *ci, ChannelInfo *target_ci)
	{
		for (const auto &[priv, level] : ci->GetLevelEntries())
		{
			target_ci->SetLevel(priv, level);
		}

		source.Reply(_("All level entries from \002%s\002 have been cloned into \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
	}

public:
	CommandCSClone(Module *creator) : Command(creator, "chanserv/clone", 2, 3)
	{
		this->SetDesc(_("Copy all settings from one channel to another"));
		this->SetSyntax(_("\037channel\037 \037target\037 [\037what\037]"));
	}

	void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
	{
		const Anope::string &channel = params[0];
		const Anope::string &target = params[1];
		Anope::string what = params.size() > 2 ? params[2] : "";

		if (Anope::ReadOnly)
		{
			source.Reply(READ_ONLY_MODE);
			return;
		}

		User *u = source.GetUser();
		ChannelInfo *ci = ChannelInfo::Find(channel);
		bool override = false;

		if (ci == NULL)
		{
			source.Reply(CHAN_X_NOT_REGISTERED, channel.c_str());
			return;
		}

		ChannelInfo *target_ci = ChannelInfo::Find(target);
		if (!target_ci)
		{
			source.Reply(CHAN_X_NOT_REGISTERED, target.c_str());
			return;
		}

		if (ci == target_ci)
		{
			source.Reply(_("Cannot clone channel \002%s\002 to itself!"), target.c_str());
			return;
		}

		if (!source.IsFounder(ci) || !source.IsFounder(target_ci))
		{
			if (!source.HasPriv("chanserv/administration"))
			{
				source.Reply(ACCESS_DENIED);
				return;
			}
			else
				override = true;
		}

		if (what.equals_ci("ALL"))
			what.clear();

		if (what.empty())
		{
			delete target_ci;
			target_ci = new ChannelInfo(*ci);
			target_ci->name = target;
			target_ci->registered = Anope::CurTime;
			(*RegisteredChannelList)[target_ci->name] = target_ci;
			target_ci->c = Channel::Find(target_ci->name);

			target_ci->bi = NULL;
			if (ci->bi)
				ci->bi->Assign(u, target_ci);

			if (target_ci->c)
			{
				target_ci->c->ci = target_ci;

				target_ci->c->CheckModes();

				target_ci->c->SetCorrectModes(u, true);
			}

			if (target_ci->c && !target_ci->c->topic.empty())
			{
				target_ci->last_topic = target_ci->c->topic;
				target_ci->last_topic_setter = target_ci->c->topic_setter;
				target_ci->last_topic_time = target_ci->c->topic_time;
			}
			else
				target_ci->last_topic_setter = source.service->nick;

			const Anope::string settings[] = { "NOAUTOOP", "CS_KEEP_MODES", "PEACE", "PERSIST", "RESTRICTED",
				"SECUREFOUNDER", "SECUREOPS", "SIGNKICK", "SIGNKICK_LEVEL", "CS_NO_EXPIRE" };

			for (const auto &setting : settings)
				CopySetting(ci, target_ci, setting);

			CopyAccess(source, ci, target_ci);
			CopyAkick(source, ci, target_ci);
			CopyBadwords(source, ci, target_ci);
			CopyLevels(source, ci, target_ci);

			FOREACH_MOD(OnChanRegistered, (target_ci));

			source.Reply(_("All settings from \002%s\002 have been cloned to \002%s\002."), ci->name.c_str(), target_ci->name.c_str());
		}
		else if (what.equals_ci("ACCESS"))
		{
			CopyAccess(source, ci, target_ci);
		}
		else if (what.equals_ci("AKICK"))
		{
			CopyAkick(source, ci, target_ci);
		}
		else if (what.equals_ci("BADWORDS"))
		{
			CopyBadwords(source, ci, target_ci);
		}
		else if (what.equals_ci("LEVELS"))
		{
			CopyLevels(source, ci, target_ci);
		}
		else
		{
			this->OnSyntaxError(source, "");
			return;
		}

		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clone " << (what.empty() ? "everything from it" : what) << " to " << target_ci->name;
	}

	bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
	{
		this->SendSyntax(source);
		source.Reply(" ");
		source.Reply(_(
			"Copies all settings, access, akicks, etc from \002channel\002 to the "
			"\002target\002 channel. If \037what\037 is \002ACCESS\002, \002AKICK\002, \002BADWORDS\002, "
			"or \002LEVELS\002 then only the respective settings are cloned. "
			"You must be the founder of \037channel\037 and \037target\037."
		));
		return true;
	}
};

class CSClone final
	: public Module
{
	CommandCSClone commandcsclone;

public:
	CSClone(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), commandcsclone(this)
	{

	}
};

MODULE_INIT(CSClone)
