#include "boost/beast/core/buffers_to_string.hpp"
#include "boost/beast/http/serializer.hpp"
#include "file_test_utilities.hpp"
#include "http/http_body.hpp"
#include "http/http_response.hpp"
#include "utility.hpp"

#include <boost/beast/core/file_base.hpp>
#include <boost/beast/core/file_posix.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/status.hpp>

#include <cstdio>
#include <filesystem>
#include <string>

#include "gtest/gtest.h"
namespace
{
void addHeaders(crow::Response& res)
{
    res.addHeader("myheader", "myvalue");
    res.keepAlive(true);
    res.result(boost::beast::http::status::ok);
}
void verifyHeaders(crow::Response& res)
{
    EXPECT_EQ(res.getHeaderValue("myheader"), "myvalue");
    EXPECT_EQ(res.keepAlive(), true);
    EXPECT_EQ(res.result(), boost::beast::http::status::ok);
}

std::string getData(boost::beast::http::response<bmcweb::HttpBody>& m)
{
    std::string ret;

    boost::beast::http::response_serializer<bmcweb::HttpBody> sr{m};
    sr.split(true);

    auto reader = [&sr, &ret](const boost::system::error_code& ec2,
                              const auto& buffer) {
        EXPECT_FALSE(ec2);
        std::string ret2 = boost::beast::buffers_to_string(buffer);
        sr.consume(ret2.size());
        ret += ret2;
    };
    boost::system::error_code ec;

    // Read headers
    while (!sr.is_header_done())
    {
        sr.next(ec, reader);
        EXPECT_FALSE(ec);
    }
    ret.clear();

    // Read body
    while (!sr.is_done())
    {
        sr.next(ec, reader);
        EXPECT_FALSE(ec);
    }

    return ret;
}

TEST(HttpResponse, Headers)
{
    crow::Response res;
    addHeaders(res);
    verifyHeaders(res);
}
TEST(HttpResponse, StringBody)
{
    crow::Response res;
    addHeaders(res);
    std::string_view bodyvalue = "this is my new body";
    res.write({bodyvalue.data(), bodyvalue.length()});
    EXPECT_EQ(*res.body(), bodyvalue);
    verifyHeaders(res);
}
TEST(HttpResponse, HttpBody)
{
    crow::Response res;
    addHeaders(res);
    std::string path = makeFile("sample text");
    res.openFile(path);

    verifyHeaders(res);
    std::filesystem::remove(path);
}
TEST(HttpResponse, HttpBodyWithFd)
{
    crow::Response res;
    addHeaders(res);
    std::string path = makeFile("sample text");
    FILE* fd = fopen(path.c_str(), "r+");
    res.openFd(fileno(fd));
    verifyHeaders(res);
    fclose(fd);
    std::filesystem::remove(path);
}

TEST(HttpResponse, Base64HttpBodyWithFd)
{
    crow::Response res;
    addHeaders(res);
    std::string path = makeFile("sample text");
    FILE* fd = fopen(path.c_str(), "r+");
    res.openFd(fileno(fd), bmcweb::EncodingType::Base64);
    verifyHeaders(res);
    fclose(fd);
    std::filesystem::remove(path);
}

TEST(HttpResponse, BodyTransitions)
{
    crow::Response res;
    addHeaders(res);
    std::string path = makeFile("sample text");
    res.openFile(path);

    verifyHeaders(res);
    res.write("body text");

    verifyHeaders(res);
    std::filesystem::remove(path);
}

void testFileData(crow::Response& res, const std::string& data)
{
    auto& fb = res.response;
    EXPECT_EQ(getData(fb), data);
}

std::string generateBigdata()
{
    std::string result;
    while (result.size() < 10000)
    {
        result += "sample text";
    }
    return result;
}

TEST(HttpResponse, StringBodyWriterLarge)
{
    crow::Response res;
    std::string data = generateBigdata();
    res.write(std::string(data));
    testFileData(res, data);
}

TEST(HttpResponse, Base64HttpBodyWriter)
{
    crow::Response res;
    std::string data = "sample text";
    std::string path = makeFile(data);
    FILE* f = fopen(path.c_str(), "r+");
    res.openFd(fileno(f), bmcweb::EncodingType::Base64);
    testFileData(res, crow::utility::base64encode(data));
    fclose(f);
    std::filesystem::remove(path);
}

TEST(HttpResponse, Base64HttpBodyWriterLarge)
{
    crow::Response res;
    std::string data = generateBigdata();
    std::string path = makeFile(data);
    {
        boost::beast::file_posix file;
        boost::system::error_code ec;
        file.open(path.c_str(), boost::beast::file_mode::read, ec);
        EXPECT_EQ(ec.value(), 0);
        res.openFd(file.native_handle(), bmcweb::EncodingType::Base64);
        testFileData(res, crow::utility::base64encode(data));
    }

    std::filesystem::remove(path);
}

TEST(HttpResponse, HttpBodyWriterLarge)
{
    crow::Response res;
    std::string data = generateBigdata();
    std::string path = makeFile(data);
    {
        boost::beast::file_posix file;
        boost::system::error_code ec;
        file.open(path.c_str(), boost::beast::file_mode::read, ec);
        EXPECT_EQ(ec.value(), 0);
        res.openFd(file.native_handle());
        testFileData(res, data);
    }
    std::filesystem::remove(path);
}

} // namespace