#include <std_include.hpp>

#include "auth3_server.hpp"
#include "../keys.hpp"

#include <utilities/cryptography.hpp>
#include <utilities/string.hpp>

namespace demonware
{
	namespace
	{
#pragma pack(push, 1)
		struct auth_ticket
		{
			unsigned int m_magicNumber;
			char m_type;
			unsigned int m_titleID;
			unsigned int m_timeIssued;
			unsigned int m_timeExpires;
			unsigned __int64 m_licenseID;
			unsigned __int64 m_userID;
			char m_username[64];
			char m_sessionKey[24];
			char m_usingHashMagicNumber[3];
			char m_hash[4];
		};
#pragma pack(pop)
	}

	void auth3_server::send_reply(reply* data)
	{
		if (!data) return;
		this->send(data->data());
	}

	void auth3_server::handle(const std::string& packet)
	{
		if (packet.starts_with("POST /auth/"))
		{
#ifndef NDEBUG
			logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [auth]: user requested authentication.");
#endif
			return;
		}

		unsigned int title_id = 0;
		unsigned int iv_seed = 0;
		std::string identity{};
		std::string session_token{};
		std::string account_token{};

		rapidjson::Document j;
		j.Parse(packet.data(), packet.size());

		if (j.HasMember("title_id") && j["title_id"].IsString())
		{
			title_id = std::stoul(j["title_id"].GetString());
		}

		if (j.HasMember("iv_seed") && j["iv_seed"].IsString())
		{
			iv_seed = std::stoul(j["iv_seed"].GetString());
		}

		if (j.HasMember("identity") && j["identity"].IsString())
		{
			identity = j["identity"].GetString();
		}

		if (j.HasMember("extra_data") && j["extra_data"].IsString())
		{
			rapidjson::Document extra_data;
			auto& ed = j["extra_data"];
			extra_data.Parse(ed.GetString(), ed.GetStringLength());

			if (extra_data.HasMember("session_token") && extra_data["session_token"].IsString())
			{
				auto& token_field = extra_data["session_token"];
				std::string token_b64(token_field.GetString(), token_field.GetStringLength());
				session_token = token_b64;
			}
			if (extra_data.HasMember("account_token") && extra_data["account_token"].IsString())
			{
				auto& token_field = extra_data["account_token"];
				std::string token_b64(token_field.GetString(), token_field.GetStringLength());
				account_token = utilities::cryptography::base64::decode(token_b64);
			}
		}

		std::string session_key(
			"\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37", 24);

		// client_ticket
		auth_ticket ticket{};
		std::memset(&ticket, 0x0, sizeof ticket);
		ticket.m_magicNumber = 0xEFBDADDE;
		ticket.m_type = 0;
		ticket.m_titleID = title_id;
		ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
		ticket.m_timeExpires = ticket.m_timeIssued + 30000;
		ticket.m_licenseID = 0;
		ticket.m_userID = 0;
		strncpy_s(ticket.m_username, sizeof(ticket.m_username), session_token.data(), session_token.length());
		std::memcpy(ticket.m_sessionKey, session_key.data(), 24);

		const auto client_ticket_b64 = utilities::cryptography::base64::encode(reinterpret_cast<const unsigned char*>(&ticket), sizeof(ticket));

		// server_ticket
		uint8_t server_ticket[128];
		std::memset(&server_ticket, 0, sizeof server_ticket);
		std::memcpy(server_ticket, session_key.data(), 24);
		const auto server_ticket_b64 = utilities::cryptography::base64::encode(server_ticket, 128);

		demonware::set_session_key(session_key);

		// header time
		char date[64];
		const auto now = time(nullptr);
		tm gmtm{};
		gmtime_s(&gmtm, &now);
		strftime(date, 64, "%a, %d %b %G %T", &gmtm);

		rapidjson::Document extra;
		extra.SetObject();

		std::string username = std::string(ticket.m_username, sizeof(ticket.m_username)).data();
		extra.AddMember("username", username, extra.GetAllocator());
		extra.AddMember("time_to_live", 9999, extra.GetAllocator());

		const auto lul = utilities::cryptography::base64::encode("lul");
		extra.AddMember("extended_data", lul, extra.GetAllocator());

		rapidjson::StringBuffer extra_buffer{};
		rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
			extra_writer(extra_buffer);
		extra.Accept(extra_writer);

		std::string extra_data{};
		extra_data.append(extra_buffer.GetString(), extra_buffer.GetLength());
		// json content
		rapidjson::Document doc;
		doc.SetObject();

		doc.AddMember("auth_task", "85", doc.GetAllocator());
		doc.AddMember("code", "700", doc.GetAllocator());

		auto seed = std::to_string(iv_seed);
		doc.AddMember("iv_seed", seed, doc.GetAllocator());
		doc.AddMember("client_ticket", client_ticket_b64, doc.GetAllocator());
		doc.AddMember("server_ticket", server_ticket_b64, doc.GetAllocator());
		doc.AddMember("client_id", "treyarch-cod-t8-bnet", doc.GetAllocator());
		doc.AddMember("account_type", "bnet", doc.GetAllocator());
		doc.AddMember("crossplay_enabled", false, doc.GetAllocator());
		doc.AddMember("loginqueue_eanbled", false, doc.GetAllocator());
		doc.AddMember("identity", identity, doc.GetAllocator());
		doc.AddMember("extra_data", extra_data, doc.GetAllocator());
		doc.AddMember("service_level", "paid", doc.GetAllocator());

		rapidjson::Value value{};
		doc.AddMember("lsg_endpoint", value, doc.GetAllocator());

		rapidjson::StringBuffer buffer{};
		rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
			writer(buffer);
		doc.Accept(writer);

		// http stuff
		std::string result;
		result.append("HTTP/1.1 200 OK\r\n");
		result.append("Server: TornadoServer/4.5.3\r\n");
		result.append("Content-Type: application/json\r\n");
		result.append(utilities::string::va("Date: %s GMT\r\n", date));
		result.append(utilities::string::va("Content-Length: %d\r\n\r\n", buffer.GetLength()));
		result.append(buffer.GetString(), buffer.GetLength());

		raw_reply reply(result);

		this->send_reply(&reply);

#ifndef NDEBUG
		logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [auth]: user successfully authenticated.");
#endif
	}
}