#pragma once

#include "app.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "utils/collection.hpp"
#include "utils/hex_utils.hpp"
#include "utils/json_utils.hpp"

#include <boost/system/error_code.hpp>
#include <boost/url/format.hpp>
#include <nlohmann/json.hpp>

#include <array>
#include <string_view>
#include <vector>

namespace crow
{
namespace google_api
{

inline void
    handleGoogleV1Get(const crow::Request& /*req*/,
                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
    asyncResp->res.jsonValue["@odata.type"] =
        "#GoogleServiceRoot.v1_0_0.GoogleServiceRoot";
    asyncResp->res.jsonValue["@odata.id"] = "/google/v1";
    asyncResp->res.jsonValue["Id"] = "Google Rest RootService";
    asyncResp->res.jsonValue["Name"] = "Google Service Root";
    asyncResp->res.jsonValue["Version"] = "1.0.0";
    asyncResp->res.jsonValue["RootOfTrustCollection"]["@odata.id"] =
        "/google/v1/RootOfTrustCollection";
}

inline void handleRootOfTrustCollectionGet(
    const crow::Request& /*req*/,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
    asyncResp->res.jsonValue["@odata.id"] = "/google/v1/RootOfTrustCollection";
    asyncResp->res.jsonValue["@odata.type"] =
        "#RootOfTrustCollection.RootOfTrustCollection";
    const std::array<std::string_view, 1> interfaces{
        "xyz.openbmc_project.Control.Hoth"};
    redfish::collection_util::getCollectionMembers(
        asyncResp, boost::urls::url("/google/v1/RootOfTrustCollection"),
        interfaces, "/xyz/openbmc_project");
}

// Helper struct to identify a resolved D-Bus object interface
struct ResolvedEntity
{
    std::string id;
    std::string service;
    std::string object;
    std::string interface;
};

using ResolvedEntityHandler = std::function<void(
    const std::string&, const std::shared_ptr<bmcweb::AsyncResp>&,
    const ResolvedEntity&)>;

inline void hothGetSubtreeCallback(
    const std::string& command,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& rotId, const ResolvedEntityHandler& entityHandler,
    const boost::system::error_code& ec,
    const dbus::utility::MapperGetSubTreeResponse& subtree)
{
    if (ec)
    {
        redfish::messages::internalError(asyncResp->res);
        return;
    }
    for (const auto& [path, services] : subtree)
    {
        sdbusplus::message::object_path objPath(path);
        if (objPath.filename() != rotId || services.empty())
        {
            continue;
        }

        ResolvedEntity resolvedEntity = {
            .id = rotId,
            .service = services[0].first,
            .object = path,
            .interface = "xyz.openbmc_project.Control.Hoth"};
        entityHandler(command, asyncResp, resolvedEntity);
        return;
    }

    // Couldn't find an object with that name.  return an error
    redfish::messages::resourceNotFound(asyncResp->res, "RootOfTrust", rotId);
}

inline void resolveRoT(const std::string& command,
                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                       const std::string& rotId,
                       ResolvedEntityHandler&& entityHandler)
{
    constexpr std::array<std::string_view, 1> hothIfaces = {
        "xyz.openbmc_project.Control.Hoth"};
    dbus::utility::getSubTree(
        "/xyz/openbmc_project", 0, hothIfaces,
        [command, asyncResp, rotId,
         entityHandler{std::forward<ResolvedEntityHandler>(entityHandler)}](
            const boost::system::error_code& ec,
            const dbus::utility::MapperGetSubTreeResponse& subtree) {
        hothGetSubtreeCallback(command, asyncResp, rotId, entityHandler, ec,
                               subtree);
    });
}

inline void populateRootOfTrustEntity(
    const std::string& /*unused*/,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const ResolvedEntity& resolvedEntity)
{
    asyncResp->res.jsonValue["@odata.type"] = "#RootOfTrust.v1_0_0.RootOfTrust";
    asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
        "/google/v1/RootOfTrustCollection/{}", resolvedEntity.id);

    asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
    asyncResp->res.jsonValue["Id"] = resolvedEntity.id;
    // Need to fix this later to a stabler property.
    asyncResp->res.jsonValue["Name"] = resolvedEntity.id;
    asyncResp->res.jsonValue["Description"] = "Google Root Of Trust";
    asyncResp->res.jsonValue["Actions"]["#RootOfTrust.SendCommand"]["target"] =
        "/google/v1/RootOfTrustCollection/" + resolvedEntity.id +
        "/Actions/RootOfTrust.SendCommand";

    asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
        resolvedEntity.id;
    asyncResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] =
        "Embedded";
}

inline void
    handleRootOfTrustGet(const crow::Request& /*req*/,
                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                         const std::string& param)
{
    std::string emptyCommand;
    resolveRoT(emptyCommand, asyncResp, param, populateRootOfTrustEntity);
}

inline void
    invocationCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                       const boost::system::error_code& ec,
                       const std::vector<uint8_t>& responseBytes)
{
    if (ec)
    {
        BMCWEB_LOG_ERROR("RootOfTrust.Actions.SendCommand failed: {}",
                         ec.message());
        redfish::messages::internalError(asyncResp->res);
        return;
    }

    asyncResp->res.jsonValue["CommandResponse"] =
        bytesToHexString(responseBytes);
}

inline void
    invokeRoTCommand(const std::string& command,
                     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                     const ResolvedEntity& resolvedEntity)
{
    std::vector<uint8_t> bytes = hexStringToBytes(command);
    if (bytes.empty())
    {
        BMCWEB_LOG_DEBUG("Invalid command: {}", command);
        redfish::messages::actionParameterValueTypeError(command, "Command",
                                                         "SendCommand");
        return;
    }

    crow::connections::systemBus->async_method_call(
        [asyncResp{asyncResp}](const boost::system::error_code& ec,
                               const std::vector<uint8_t>& responseBytes) {
        invocationCallback(asyncResp, ec, responseBytes);
    },
        resolvedEntity.service, resolvedEntity.object, resolvedEntity.interface,
        "SendHostCommand", bytes);
}

inline void handleRoTSendCommandPost(
    const crow::Request& request,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& rotId)
{
    std::string command;
    if (!redfish::json_util::readJsonAction(request, asyncResp->res, "Command",
                                            command))
    {
        BMCWEB_LOG_DEBUG("Missing property Command.");
        redfish::messages::actionParameterMissing(asyncResp->res, "SendCommand",
                                                  "Command");
        return;
    }

    resolveRoT(command, asyncResp, rotId, invokeRoTCommand);
}

inline void requestRoutes(App& app)
{
    BMCWEB_ROUTE(app, "/google/v1/")
        .methods(boost::beast::http::verb::get)(handleGoogleV1Get);

    BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection")
        .privileges({{"ConfigureManager"}})
        .methods(boost::beast::http::verb::get)(handleRootOfTrustCollectionGet);

    BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/<str>")
        .privileges({{"ConfigureManager"}})
        .methods(boost::beast::http::verb::get)(handleRootOfTrustGet);

    BMCWEB_ROUTE(
        app,
        "/google/v1/RootOfTrustCollection/<str>/Actions/RootOfTrust.SendCommand")
        .privileges({{"ConfigureManager"}})
        .methods(boost::beast::http::verb::post)(handleRoTSendCommandPost);
}

} // namespace google_api
} // namespace crow