/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */

#include "base/base64.hpp"
#include "base/json.hpp"
#include "remote/httpmessage.hpp"
#include "remote/httputility.hpp"
#include "test/base-tlsstream-fixture.hpp"
#include <BoostTestTargetConfig.h>
#include <fstream>

using namespace icinga;
using namespace boost::beast;

BOOST_FIXTURE_TEST_SUITE(remote_httpmessage, TlsStreamFixture)

BOOST_AUTO_TEST_CASE(request_parse)
{
	http::request<boost::beast::http::string_body> requestOut;
	requestOut.method(http::verb::get);
	requestOut.target("https://localhost:5665/v1/test");
	requestOut.set(http::field::authorization, "Basic " + Base64::Encode("invalid:invalid"));
	requestOut.set(http::field::accept, "application/json");
	requestOut.set(http::field::connection, "close");
	requestOut.body() = "test";
	requestOut.prepare_payload();

	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this, &requestOut](boost::asio::yield_context yc) {
		boost::beast::flat_buffer buf;
		HttpRequest request(server);
		BOOST_REQUIRE_NO_THROW(request.ParseHeader(buf, yc));

		for (auto& field : requestOut.base()) {
			BOOST_REQUIRE(request.count(field.name()));
		}

		BOOST_REQUIRE_NO_THROW(request.ParseBody(buf, yc));
		BOOST_REQUIRE_EQUAL(request.body(), "test");

		Shutdown(server, yc);
	});

	http::write(*client, requestOut);
	client->flush();

	Shutdown(client);
}

BOOST_AUTO_TEST_CASE(request_params)
{
	HttpRequest request(client);
	request.body() = JsonEncode(new Dictionary{{"test1", false}, {"test2", true}});
	request.target("https://localhost:1234/v1/test?test1=1&test3=0&test3=1&test4=0");
	request.DecodeParams();

	auto params = request.Params();
	BOOST_REQUIRE(params);
	BOOST_REQUIRE(!params->Get("test2").IsObjectType<Array>());
	BOOST_REQUIRE(params->Get("test2").IsBoolean());
	BOOST_REQUIRE(params->Get("test2"));
	BOOST_REQUIRE(params->Get("test1").IsObjectType<Array>());
	BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "test1"));
	BOOST_REQUIRE(params->Get("test3").IsObjectType<Array>());
	BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "test3"));
	BOOST_REQUIRE(params->Get("test4").IsObjectType<Array>());
	BOOST_REQUIRE(!HttpUtility::GetLastParameter(params, "test4"));
}

BOOST_AUTO_TEST_CASE(response_flush_nothrow)
{
	auto& io = IoEngine::Get();
	std::promise<void> done;

	io.SpawnCoroutine(io.GetIoContext(), [this, &done](boost::asio::yield_context yc) {
		HttpResponse response(server);
		response.result(http::status::ok);

		server->lowest_layer().close();

		boost::beast::error_code ec;
		BOOST_REQUIRE_NO_THROW(response.Flush(yc[ec]));
		BOOST_REQUIRE_EQUAL(ec, boost::system::errc::bad_file_descriptor);

		done.set_value();
	});

	auto status = done.get_future().wait_for(std::chrono::seconds(1));

	BOOST_REQUIRE(status == std::future_status::ready);
}

BOOST_AUTO_TEST_CASE(response_flush_throw)
{
	auto& io = IoEngine::Get();
	std::promise<void> done;

	io.SpawnCoroutine(io.GetIoContext(), [this, &done](boost::asio::yield_context yc) {
		HttpResponse response(server);
		response.result(http::status::ok);

		server->lowest_layer().close();

		BOOST_REQUIRE_EXCEPTION(response.Flush(yc), std::exception, [](const std::exception& ex) {
			auto se = dynamic_cast<const boost::system::system_error*>(&ex);
			return se && se->code() == boost::system::errc::bad_file_descriptor;
		});

		done.set_value();
	});

	auto status = done.get_future().wait_for(std::chrono::seconds(1));

	BOOST_REQUIRE(status == std::future_status::ready);
}

BOOST_AUTO_TEST_CASE(response_write_empty)
{
	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) {
		HttpResponse response(server);
		response.result(http::status::ok);

		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		Shutdown(server, yc);
	});

	http::response_parser<http::string_body> parser;
	flat_buffer buf;
	boost::system::error_code ec;
	http::read(*client, buf, parser, ec);

	Shutdown(client);

	BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
	BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);
	BOOST_REQUIRE_EQUAL(parser.get().body(), "");
}

BOOST_AUTO_TEST_CASE(response_write_fixed)
{
	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) {
		HttpResponse response(server);
		response.result(http::status::ok);
		response.body() << "test";

		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		Shutdown(server, yc);
	});

	http::response_parser<http::string_body> parser;
	flat_buffer buf;
	boost::system::error_code ec;
	http::read(*client, buf, parser, ec);

	Shutdown(client);

	BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
	BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);
	BOOST_REQUIRE_EQUAL(parser.get().body(), "test");
}

BOOST_AUTO_TEST_CASE(response_write_chunked)
{
	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) {
		HttpResponse response(server);
		response.result(http::status::ok);

		response.StartStreaming();
		response.body() << "test" << 1;
		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		response.body() << "test" << 2;
		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		response.body() << "test" << 3;
		response.body().Finish();
		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		Shutdown(server, yc);
	});

	http::response_parser<http::string_body> parser;
	flat_buffer buf;
	boost::system::error_code ec;
	http::read(*client, buf, parser, ec);

	Shutdown(client);

	BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
	BOOST_REQUIRE_EQUAL(parser.get().chunked(), true);
	BOOST_REQUIRE_EQUAL(parser.get().body(), "test1test2test3");
}

BOOST_AUTO_TEST_CASE(response_sendjsonbody)
{
	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) {
		HttpResponse response(server);
		response.result(http::status::ok);

		HttpUtility::SendJsonBody(response, nullptr, new Dictionary{{"test", 1}});

		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		Shutdown(server, yc);
	});

	http::response_parser<http::string_body> parser;
	flat_buffer buf;
	boost::system::error_code ec;
	http::read(*client, buf, parser, ec);

	Shutdown(client);

	BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
	BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);
	Dictionary::Ptr body = JsonDecode(parser.get().body());
	BOOST_REQUIRE_EQUAL(body->Get("test"), 1);
}

BOOST_AUTO_TEST_CASE(response_sendjsonerror)
{
	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) {
		HttpResponse response(server);

		// This has to be overwritten in SendJsonError.
		response.result(http::status::ok);

		HttpUtility::SendJsonError(response, nullptr, 404, "Not found.");

		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		Shutdown(server, yc);
	});

	http::response_parser<http::string_body> parser;
	flat_buffer buf;
	boost::system::error_code ec;
	http::read(*client, buf, parser, ec);

	Shutdown(client);

	BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::not_found);
	BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);
	Dictionary::Ptr body = JsonDecode(parser.get().body());
	BOOST_REQUIRE_EQUAL(body->Get("error"), 404);
	BOOST_REQUIRE_EQUAL(body->Get("status"), "Not found.");
}

BOOST_AUTO_TEST_CASE(response_sendFile)
{
	auto& io = IoEngine::Get();
	io.SpawnCoroutine(io.GetIoContext(), [this](boost::asio::yield_context yc) {
		HttpResponse response(server);

		response.result(http::status::ok);
		BOOST_REQUIRE_NO_THROW(response.SendFile(m_CaCrtFile.string(), yc));
		BOOST_REQUIRE_NO_THROW(response.Flush(yc));

		Shutdown(server, yc);
	});

	http::response_parser<http::string_body> parser;
	flat_buffer buf;
	boost::system::error_code ec;
	http::read(*client, buf, parser, ec);

	Shutdown(client);

	BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok);
	BOOST_REQUIRE_EQUAL(parser.get().chunked(), false);

	std::ifstream fp(m_CaCrtFile.string(), std::ifstream::in | std::ifstream::binary);
	fp.exceptions(std::ifstream::badbit);
	std::stringstream ss;
	ss << fp.rdbuf();
	BOOST_REQUIRE_EQUAL(ss.str(), parser.get().body());
}

BOOST_AUTO_TEST_SUITE_END()
