/*
* Copyright (c) 2013-2025, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/

#include <iomanip>
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/lexical_cast.hpp>
#include <boost/property_tree/json_parser.hpp>

#include "Log.h"
#include "RouterContext.h"
#include "NetDb.hpp"
#include "Tunnel.h"
#include "Transports.h"
#include "version.h"
#include "ClientContext.h"
#include "I2PControlHandlers.h"

namespace i2p
{
namespace client
{
	I2PControlHandlers::I2PControlHandlers ()
	{
		// RouterInfo
		m_RouterInfoHandlers["i2p.router.uptime"]                    = &I2PControlHandlers::UptimeHandler;
		m_RouterInfoHandlers["i2p.router.version"]                   = &I2PControlHandlers::VersionHandler;
		m_RouterInfoHandlers["i2p.router.status"]                    = &I2PControlHandlers::StatusHandler;
		m_RouterInfoHandlers["i2p.router.netdb.knownpeers"]          = &I2PControlHandlers::NetDbKnownPeersHandler;
		m_RouterInfoHandlers["i2p.router.netdb.activepeers"]         = &I2PControlHandlers::NetDbActivePeersHandler;
		m_RouterInfoHandlers["i2p.router.netdb.floodfills"]          = &I2PControlHandlers::NetDbFloodfillsHandler;
		m_RouterInfoHandlers["i2p.router.netdb.leasesets"]           = &I2PControlHandlers::NetDbLeaseSetsHandler;
		m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"]         = &I2PControlHandlers::InboundBandwidth1S;
		m_RouterInfoHandlers["i2p.router.net.bw.inbound.15s"]        = &I2PControlHandlers::InboundBandwidth15S;
		m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"]        = &I2PControlHandlers::OutboundBandwidth1S;
		m_RouterInfoHandlers["i2p.router.net.bw.outbound.15s"]       = &I2PControlHandlers::OutboundBandwidth15S;
		m_RouterInfoHandlers["i2p.router.net.status"]                = &I2PControlHandlers::NetStatusHandler;
		m_RouterInfoHandlers["i2p.router.net.status.v6"]             = &I2PControlHandlers::NetStatusV6Handler;
		m_RouterInfoHandlers["i2p.router.net.error"]                 = &I2PControlHandlers::NetErrorHandler;
		m_RouterInfoHandlers["i2p.router.net.error.v6"]              = &I2PControlHandlers::NetErrorV6Handler;
		m_RouterInfoHandlers["i2p.router.net.testing"]               = &I2PControlHandlers::NetTestingHandler;
		m_RouterInfoHandlers["i2p.router.net.testing.v6"]            = &I2PControlHandlers::NetTestingV6Handler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlHandlers::TunnelsParticipatingHandler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.inbound"]       = &I2PControlHandlers::TunnelsInboundHandler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.outbound"]      = &I2PControlHandlers::TunnelsOutboundHandler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"]   = &I2PControlHandlers::TunnelsSuccessRateHandler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.totalsuccessrate"] = &I2PControlHandlers::TunnelsTotalSuccessRateHandler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.queue"]         = &I2PControlHandlers::TunnelsQueueHandler;
		m_RouterInfoHandlers["i2p.router.net.tunnels.tbmqueue"]      = &I2PControlHandlers::TunnelsTBMQueueHandler;
		m_RouterInfoHandlers["i2p.router.net.total.received.bytes"]  = &I2PControlHandlers::NetTotalReceivedBytes;
		m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"]      = &I2PControlHandlers::NetTotalSentBytes;
		m_RouterInfoHandlers["i2p.router.net.total.transit.bytes"]   = &I2PControlHandlers::NetTotalTransitBytes;
		m_RouterInfoHandlers["i2p.router.net.bw.transit.15s"]        = &I2PControlHandlers::TransitBandwidth15S;

		// NetworkSetting
		m_NetworkSettingHandlers["i2p.router.net.bw.in"]  = &I2PControlHandlers::InboundBandwidthLimit;
		m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlHandlers::OutboundBandwidthLimit;

		// ClientServicesInfo
		m_ClientServicesInfoHandlers["I2PTunnel"] = &I2PControlHandlers::I2PTunnelInfoHandler;
		m_ClientServicesInfoHandlers["HTTPProxy"] = &I2PControlHandlers::HTTPProxyInfoHandler;
		m_ClientServicesInfoHandlers["SOCKS"]     = &I2PControlHandlers::SOCKSInfoHandler;
		m_ClientServicesInfoHandlers["SAM"]       = &I2PControlHandlers::SAMInfoHandler;
		m_ClientServicesInfoHandlers["BOB"]       = &I2PControlHandlers::BOBInfoHandler;
		m_ClientServicesInfoHandlers["I2CP"]      = &I2PControlHandlers::I2CPInfoHandler;
	}

	void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, int value) const
	{
		ss << "\"" << name << "\":" << value;
	}

	void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value, bool quotes) const
	{
		ss << "\"" << name << "\":";
		if (value.length () > 0)
		{
			if (quotes)
				ss << "\"" << value << "\"";
			else
				ss << value;
		}
		else
			ss << "null";
	}

	void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, double value) const
	{
		ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value;
	}

	void I2PControlHandlers::InsertParam (std::ostringstream& ss, const std::string& name, const boost::property_tree::ptree& value) const
	{
		std::ostringstream buf;
		boost::property_tree::write_json (buf, value, false);
		ss << "\"" << name << "\":" << buf.str();
	}

// LocalDestinationInfo

	void I2PControlHandlers::LocalDestinationInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
	{
		auto& addressBook = i2p::client::context.GetAddressBook ();
		auto addr = addressBook.GetAddress (params.get<std::string> ("destination", ""));
		auto dest = addr && addr->IsIdentHash () ?
			i2p::client::context.FindLocalDestination (addr->identHash) : nullptr;
		if (!dest)
		{
			InsertParam (results, "error", std::string ("destination not found"));
			return;
		}
		InsertParam (results, "destination", addressBook.ToAddress (dest->GetIdentHash ()));

		// arrays are written by hand: write_json makes an array only below the root
		results << ",\"leasesets\":[";
		bool first = true;
		for (const auto& it: dest->GetLeaseSetsList ())
		{
			if (!it) continue;
			boost::property_tree::ptree ls;
			ls.put ("address", addressBook.ToAddress (it->GetIdentHash ()));
			ls.put ("type", (int)it->GetStoreType ());
			ls.put ("encType", (int)it->GetEncryptionType ());
			if (!first) results << ",";
			first = false;
			boost::property_tree::write_json (results, ls, false);
		}
		results << "],\"streams\":[";
		first = true;
		for (const auto& it: dest->GetAllStreams ())
		{
			if (!it) continue;
			auto identity = it->GetRemoteIdentity ();
			boost::property_tree::ptree st;
			st.put ("id", it->GetRecvStreamID ());
			st.put ("destination", identity ? addressBook.ToAddress (identity->GetIdentHash ()) : "");
			st.put ("sent", it->GetNumSentBytes ());
			st.put ("received", it->GetNumReceivedBytes ());
			if (!first) results << ",";
			first = false;
			boost::property_tree::write_json (results, st, false);
		}
		results << "]";
	}

// RouterInfo

	void I2PControlHandlers::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
	{
		bool first = true;
		for (auto it = params.begin (); it != params.end (); it++)
		{
			LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first);
			auto it1 = m_RouterInfoHandlers.find (it->first);
			if (it1 != m_RouterInfoHandlers.end ())
			{
				if (!first) results << ",";
				else first = false;
				(this->*(it1->second))(results);
			}
			else
				LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first);
		}
	}

	void I2PControlHandlers::UptimeHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.uptime", std::to_string (i2p::context.GetUptime ()*1000LL), false);
	}

	void I2PControlHandlers::VersionHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.version", VERSION);
	}

	void I2PControlHandlers::StatusHandler (std::ostringstream& results)
	{
		auto dest = i2p::client::context.GetSharedLocalDestination ();
		InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0");
	}

	void I2PControlHandlers::NetDbKnownPeersHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ());
	}

	void I2PControlHandlers::NetDbActivePeersHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ());
	}

	void I2PControlHandlers::NetDbFloodfillsHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.netdb.floodfills", i2p::data::netdb.GetNumFloodfills ());
	}

	void I2PControlHandlers::NetDbLeaseSetsHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.netdb.leasesets", i2p::data::netdb.GetNumLeaseSets ());
	}

	void I2PControlHandlers::NetStatusHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ());
	}

	void I2PControlHandlers::NetStatusV6Handler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.status.v6", (int)i2p::context.GetStatusV6 ());
	}

	void I2PControlHandlers::NetErrorHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.error", (int)i2p::context.GetError ());
	}

	void I2PControlHandlers::NetErrorV6Handler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.error.v6", (int)i2p::context.GetErrorV6 ());
	}

	void I2PControlHandlers::NetTestingHandler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.testing", (int)i2p::context.GetTesting ());
	}

	void I2PControlHandlers::NetTestingV6Handler (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.testing.v6", (int)i2p::context.GetTestingV6 ());
	}

	void I2PControlHandlers::TunnelsParticipatingHandler (std::ostringstream& results)
	{
		int transit = i2p::tunnel::tunnels.CountTransitTunnels ();
		InsertParam (results, "i2p.router.net.tunnels.participating", transit);
	}

	void I2PControlHandlers::TunnelsInboundHandler (std::ostringstream& results)
	{
		int inbound = i2p::tunnel::tunnels.CountInboundTunnels ();
		InsertParam (results, "i2p.router.net.tunnels.inbound", inbound);
	}

	void I2PControlHandlers::TunnelsOutboundHandler (std::ostringstream& results)
	{
		int outbound = i2p::tunnel::tunnels.CountOutboundTunnels ();
		InsertParam (results, "i2p.router.net.tunnels.outbound", outbound);
	}

	void I2PControlHandlers::TunnelsSuccessRateHandler (std::ostringstream& results)
	{
		int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate ();
		InsertParam (results, "i2p.router.net.tunnels.successrate", rate);
	}

	void I2PControlHandlers::TunnelsTotalSuccessRateHandler (std::ostringstream& results)
	{
		int rate = i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate ();
		InsertParam (results, "i2p.router.net.tunnels.totalsuccessrate", rate);
	}

	void I2PControlHandlers::TunnelsQueueHandler (std::ostringstream& results)
	{
		int queue = i2p::tunnel::tunnels.GetQueueSize ();
		InsertParam (results, "i2p.router.net.tunnels.queue", queue);
	}

	void I2PControlHandlers::TunnelsTBMQueueHandler (std::ostringstream& results)
	{
		int queue = i2p::tunnel::tunnels.GetTBMQueueSize ();
		InsertParam (results, "i2p.router.net.tunnels.tbmqueue", queue);
	}

	void I2PControlHandlers::InboundBandwidth1S (std::ostringstream& results)
	{
		double bw = i2p::transport::transports.GetInBandwidth ();
		InsertParam (results, "i2p.router.net.bw.inbound.1s", bw);
	}

	void I2PControlHandlers::InboundBandwidth15S (std::ostringstream& results)
	{
		double bw = i2p::transport::transports.GetInBandwidth15s ();
		InsertParam (results, "i2p.router.net.bw.inbound.15s", bw);
	}

	void I2PControlHandlers::OutboundBandwidth1S (std::ostringstream& results)
	{
		double bw = i2p::transport::transports.GetOutBandwidth ();
		InsertParam (results, "i2p.router.net.bw.outbound.1s", bw);
	}

	void I2PControlHandlers::OutboundBandwidth15S (std::ostringstream& results)
	{
		double bw = i2p::transport::transports.GetOutBandwidth15s ();
		InsertParam (results, "i2p.router.net.bw.outbound.15s", bw);
	}

	void I2PControlHandlers::TransitBandwidth15S (std::ostringstream& results)
	{
		double bw = i2p::transport::transports.GetTransitBandwidth15s ();
		InsertParam (results, "i2p.router.net.bw.transit.15s", bw);
	}

	void I2PControlHandlers::NetTotalReceivedBytes (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ());
	}

	void I2PControlHandlers::NetTotalSentBytes (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ());
	}

	void I2PControlHandlers::NetTotalTransitBytes (std::ostringstream& results)
	{
		InsertParam (results, "i2p.router.net.total.transit.bytes", (double)i2p::transport::transports.GetTotalTransitTransmittedBytes ());
	}

// network setting
	void I2PControlHandlers::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
	{
		for (auto it = params.begin (); it != params.end (); it++)
		{
			LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first);
			auto it1 = m_NetworkSettingHandlers.find (it->first);
			if (it1 != m_NetworkSettingHandlers.end ()) {
				if (it != params.begin ()) results << ",";
				(this->*(it1->second))(it->second.data (), results);
			} else
				LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first);
		}
	}

	void I2PControlHandlers::InboundBandwidthLimit (const std::string& value, std::ostringstream& results)
	{
		if (value != "null")
			i2p::context.SetBandwidth (std::atoi(value.c_str()));
		int bw = i2p::context.GetBandwidthLimit();
		InsertParam (results, "i2p.router.net.bw.in", bw);
	}

	void I2PControlHandlers::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results)
	{
		if (value != "null")
			i2p::context.SetBandwidth (std::atoi(value.c_str()));
		int bw = i2p::context.GetBandwidthLimit();
		InsertParam (results, "i2p.router.net.bw.out", bw);
	}

// ClientServicesInfo

	void I2PControlHandlers::ClientServicesInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results)
	{
		bool first = true;
		for (auto it = params.begin (); it != params.end (); it++)
		{
			LogPrint (eLogDebug, "I2PControl: ClientServicesInfo request: ", it->first);
			auto it1 = m_ClientServicesInfoHandlers.find (it->first);
			if (it1 != m_ClientServicesInfoHandlers.end ())
			{
				if (!first) results << ",";
				else first = false;
				(this->*(it1->second))(results);
			}
			else
				LogPrint (eLogError, "I2PControl: ClientServicesInfo unknown request ", it->first);
		}
	}

	void I2PControlHandlers::I2PTunnelInfoHandler (std::ostringstream& results)
	{
		boost::property_tree::ptree pt;
		boost::property_tree::ptree client_tunnels, server_tunnels;

		for (auto& it: i2p::client::context.GetClientTunnels ())
		{
			auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
			boost::property_tree::ptree ct;
			ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
			client_tunnels.add_child(it.second->GetName (), ct);
		}

		auto& serverTunnels = i2p::client::context.GetServerTunnels ();
		if (!serverTunnels.empty ()) {
			for (auto& it: serverTunnels)
			{
				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
				boost::property_tree::ptree st;
				st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
				st.put("port", it.second->GetLocalPort ());
				server_tunnels.add_child(it.second->GetName (), st);
			}
		}

		auto& clientForwards = i2p::client::context.GetClientForwards ();
		if (!clientForwards.empty ())
		{
			for (auto& it: clientForwards)
			{
				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
				boost::property_tree::ptree ct;
				ct.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
				client_tunnels.add_child(it.second->GetName (), ct);
			}
		}

		auto& serverForwards = i2p::client::context.GetServerForwards ();
		if (!serverForwards.empty ())
		{
			for (auto& it: serverForwards)
			{
				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
				boost::property_tree::ptree st;
				st.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
				server_tunnels.add_child(it.second->GetName (), st);
			}
		}

		pt.add_child("client", client_tunnels);
		pt.add_child("server", server_tunnels);

		InsertParam (results, "I2PTunnel", pt);
	}

	void I2PControlHandlers::HTTPProxyInfoHandler (std::ostringstream& results)
	{
		boost::property_tree::ptree pt;

		auto httpProxy = i2p::client::context.GetHttpProxy ();
		if (httpProxy)
		{
			auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash();
			pt.put("enabled", true);
			pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
		}
		else
			pt.put("enabled", false);

		InsertParam (results, "HTTPProxy", pt);
	}

	void I2PControlHandlers::SOCKSInfoHandler (std::ostringstream& results)
	{
		boost::property_tree::ptree pt;

		auto socksProxy = i2p::client::context.GetSocksProxy ();
		if (socksProxy)
		{
			auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash();
			pt.put("enabled", true);
			pt.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));
		}
		else
			pt.put("enabled", false);

		InsertParam (results, "SOCKS", pt);
	}

	void I2PControlHandlers::SAMInfoHandler (std::ostringstream& results)
	{
		boost::property_tree::ptree pt;
		auto sam = i2p::client::context.GetSAMBridge ();
		if (sam)
		{
			pt.put("enabled", true);
			boost::property_tree::ptree sam_sessions;
			for (auto& it: sam->GetSessionsList ())
			{
				boost::property_tree::ptree sam_session, sam_session_sockets;
				auto& name = it.second->GetLocalDestination ()->GetNickname ();
				auto& ident = it.second->GetLocalDestination ()->GetIdentHash();
				sam_session.put("name", name);
				sam_session.put("address", i2p::client::context.GetAddressBook ().ToAddress(ident));

				for (const auto& socket: sam->ListSockets(it.first))
				{
					boost::property_tree::ptree stream;
					stream.put("type", (int)socket->GetSocketType ());
					stream.put("peer", socket->GetSocket ().remote_endpoint());

					sam_session_sockets.push_back(std::make_pair("", stream));
				}
				sam_session.add_child("sockets", sam_session_sockets);
				sam_sessions.add_child(it.first, sam_session);
			}

			pt.add_child("sessions", sam_sessions);
		}
		else
			pt.put("enabled", false);

		InsertParam (results, "SAM", pt);
	}

	void I2PControlHandlers::BOBInfoHandler (std::ostringstream& results)
	{
		boost::property_tree::ptree pt;
		auto bob = i2p::client::context.GetBOBCommandChannel ();
		if (bob)
		{
			/* TODO more info */
			pt.put("enabled", true);
		}
		else
			pt.put("enabled", false);

		InsertParam (results, "BOB", pt);
	}

	void I2PControlHandlers::I2CPInfoHandler (std::ostringstream& results)
	{
		boost::property_tree::ptree pt;
		auto i2cp = i2p::client::context.GetI2CPServer ();
		if (i2cp)
		{
			/* TODO more info */
			pt.put("enabled", true);
		}
		else
			pt.put("enabled", false);

		InsertParam (results, "I2CP", pt);
	}
}
}
