// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
#pragma once

#include "async_resp.hpp"
#include "http_connect_types.hpp"
#include "http_request.hpp"
#include "http_server.hpp"
#include "io_context_singleton.hpp"
#include "logging.hpp"
#include "routing.hpp"
#include "routing/dynamicrule.hpp"
#include "str_utility.hpp"

#include <sys/socket.h>
#include <systemd/sd-daemon.h>

#include <boost/asio/ip/tcp.hpp>

#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage, clang-diagnostic-unused-macros)
#define BMCWEB_ROUTE(app, url)                                                 \
    app.template route<crow::utility::getParameterTag(url)>(url)

namespace crow
{
class App
{
  public:
    using raw_socket_t = boost::asio::ip::tcp::socket;
    using server_type = Server<App, raw_socket_t>;

    template <typename Adaptor>
    void handleUpgrade(const std::shared_ptr<Request>& req,
                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                       Adaptor&& adaptor)
    {
        router.handleUpgrade(req, asyncResp, std::forward<Adaptor>(adaptor));
    }

    void handle(const std::shared_ptr<Request>& req,
                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
    {
        router.handle(req, asyncResp);
    }

    DynamicRule& routeDynamic(const std::string& rule)
    {
        return router.newRuleDynamic(rule);
    }

    template <uint64_t Tag>
    auto& route(std::string&& rule)
    {
        return router.newRuleTagged<Tag>(std::move(rule));
    }

    void validate()
    {
        router.validate();
    }

    void loadCertificate()
    {
        BMCWEB_LOG_DEBUG("Loading certificate");
        if (!server)
        {
            return;
        }
        server->loadCertificate();
    }

    static HttpType getHttpType(std::string_view socketTypeString)
    {
        if (socketTypeString == "http")
        {
            BMCWEB_LOG_DEBUG("Got http socket");
            return HttpType::HTTP;
        }
        if (socketTypeString == "https")
        {
            BMCWEB_LOG_DEBUG("Got https socket");
            return HttpType::HTTPS;
        }
        if (socketTypeString == "both")
        {
            BMCWEB_LOG_DEBUG("Got hybrid socket");
            return HttpType::BOTH;
        }

        // all other types https
        BMCWEB_LOG_ERROR("Unknown http type={} assuming HTTPS only",
                         socketTypeString);
        return HttpType::HTTPS;
    }

    static std::vector<Acceptor> setupSocket()
    {
        std::vector<Acceptor> acceptors;
        char** names = nullptr;
        int listenFdCount = sd_listen_fds_with_names(0, &names);
        BMCWEB_LOG_DEBUG("Got {} sockets to open", listenFdCount);

        if (listenFdCount < 0)
        {
            BMCWEB_LOG_CRITICAL("Failed to read socket files");
            return acceptors;
        }
        int socketIndex = 0;
        for (char* name :
             std::span<char*>(names, static_cast<size_t>(listenFdCount)))
        {
            if (name == nullptr)
            {
                continue;
            }
            // name looks like bmcweb_443_https_auth
            // Assume HTTPS as default
            std::string socketName(name);

            std::vector<std::string> socknameComponents;
            bmcweb::split(socknameComponents, socketName, '_');
            HttpType httpType = getHttpType(socknameComponents[2]);

            int listenFd = socketIndex + SD_LISTEN_FDS_START;
            if (sd_is_socket_inet(listenFd, AF_UNSPEC, SOCK_STREAM, 1, 0) > 0)
            {
                BMCWEB_LOG_INFO("Starting webserver on socket handle {}",
                                listenFd);
                acceptors.emplace_back(Acceptor{
                    boost::asio::ip::tcp::acceptor(
                        getIoContext(), boost::asio::ip::tcp::v6(), listenFd),
                    httpType});
            }
            socketIndex++;
        }

        if (acceptors.empty())
        {
            constexpr int defaultPort = 18080;
            BMCWEB_LOG_INFO("Starting webserver on port {}", defaultPort);
            using boost::asio::ip::tcp;
            tcp::endpoint end(tcp::v6(), defaultPort);
            tcp::acceptor acc(getIoContext(), end);
            acceptors.emplace_back(std::move(acc), HttpType::HTTPS);
        }

        return acceptors;
    }

    void run()
    {
        validate();

        std::vector<Acceptor> acceptors = setupSocket();

        server.emplace(this, std::move(acceptors));
        server->run();
    }

    void debugPrint()
    {
        BMCWEB_LOG_DEBUG("Routing:");
        router.debugPrint();
    }

    std::vector<const std::string*> getRoutes()
    {
        const std::string root;
        return router.getRoutes(root);
    }
    std::vector<const std::string*> getRoutes(const std::string& parent)
    {
        return router.getRoutes(parent);
    }

    std::optional<server_type> server;

    Router router;
};
} // namespace crow
using App = crow::App;