/*
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#pragma once
#include "event_service_manager.hpp"

#include <app.hpp>
#include <registries/privilege_registry.hpp>

namespace redfish
{

static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
    eventFormatType, metricReportFormatType};
static constexpr const std::array<const char*, 3> supportedRegPrefixes = {
    "Base", "OpenBMC", "TaskEvent"};
static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
    "TerminateAfterRetries", "SuspendRetries", "RetryForever"};

#ifdef BMCWEB_ENABLE_IBM_MANAGEMENT_CONSOLE
static constexpr const std::array<const char*, 2> supportedResourceTypes = {
    "IBMConfigFile", "Task"};
#else
static constexpr const std::array<const char*, 1> supportedResourceTypes = {
    "Task"};
#endif

static constexpr const uint8_t maxNoOfSubscriptions = 20;

inline void requestRoutesEventService(App& app)
{
    BMCWEB_ROUTE(app, "/redfish/v1/EventService/")
        .privileges(redfish::privileges::getEventService)
        .methods(boost::beast::http::verb::get)(
            [](const crow::Request&,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
                asyncResp->res.jsonValue = {
                    {"@odata.type", "#EventService.v1_5_0.EventService"},
                    {"Id", "EventService"},
                    {"Name", "Event Service"},
                    {"Subscriptions",
                     {{"@odata.id", "/redfish/v1/EventService/Subscriptions"}}},
                    {"Actions",
                     {{"#EventService.SubmitTestEvent",
                       {{"target", "/redfish/v1/EventService/Actions/"
                                   "EventService.SubmitTestEvent"}}}}},
                    {"@odata.id", "/redfish/v1/EventService"}};

                const persistent_data::EventServiceConfig eventServiceConfig =
                    persistent_data::EventServiceStore::getInstance()
                        .getEventServiceConfig();

                asyncResp->res.jsonValue["Status"]["State"] =
                    (eventServiceConfig.enabled ? "Enabled" : "Disabled");
                asyncResp->res.jsonValue["ServiceEnabled"] =
                    eventServiceConfig.enabled;
                asyncResp->res.jsonValue["DeliveryRetryAttempts"] =
                    eventServiceConfig.retryAttempts;
                asyncResp->res.jsonValue["DeliveryRetryIntervalSeconds"] =
                    eventServiceConfig.retryTimeoutInterval;
                asyncResp->res.jsonValue["EventFormatTypes"] =
                    supportedEvtFormatTypes;
                asyncResp->res.jsonValue["RegistryPrefixes"] =
                    supportedRegPrefixes;
                asyncResp->res.jsonValue["ResourceTypes"] =
                    supportedResourceTypes;

                nlohmann::json supportedSSEFilters = {
                    {"EventFormatType", true},        {"MessageId", true},
                    {"MetricReportDefinition", true}, {"RegistryPrefix", true},
                    {"OriginResource", false},        {"ResourceType", false}};

                asyncResp->res.jsonValue["SSEFilterPropertiesSupported"] =
                    supportedSSEFilters;
            });

    BMCWEB_ROUTE(app, "/redfish/v1/EventService/")
        .privileges(redfish::privileges::patchEventService)
        .methods(boost::beast::http::verb::patch)(
            [](const crow::Request& req,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)

            {
                std::optional<bool> serviceEnabled;
                std::optional<uint32_t> retryAttemps;
                std::optional<uint32_t> retryInterval;

                if (!json_util::readJson(
                        req, asyncResp->res, "ServiceEnabled", serviceEnabled,
                        "DeliveryRetryAttempts", retryAttemps,
                        "DeliveryRetryIntervalSeconds", retryInterval))
                {
                    return;
                }

                persistent_data::EventServiceConfig eventServiceConfig =
                    persistent_data::EventServiceStore::getInstance()
                        .getEventServiceConfig();

                if (serviceEnabled)
                {
                    eventServiceConfig.enabled = *serviceEnabled;
                }

                if (retryAttemps)
                {
                    // Supported range [1-3]
                    if ((*retryAttemps < 1) || (*retryAttemps > 3))
                    {
                        messages::queryParameterOutOfRange(
                            asyncResp->res, std::to_string(*retryAttemps),
                            "DeliveryRetryAttempts", "[1-3]");
                    }
                    else
                    {
                        eventServiceConfig.retryAttempts = *retryAttemps;
                    }
                }

                if (retryInterval)
                {
                    // Supported range [30 - 180]
                    if ((*retryInterval < 30) || (*retryInterval > 180))
                    {
                        messages::queryParameterOutOfRange(
                            asyncResp->res, std::to_string(*retryInterval),
                            "DeliveryRetryIntervalSeconds", "[30-180]");
                    }
                    else
                    {
                        eventServiceConfig.retryTimeoutInterval =
                            *retryInterval;
                    }
                }

                EventServiceManager::getInstance().setEventServiceConfig(
                    eventServiceConfig);
            });
}

inline void requestRoutesSubmitTestEvent(App& app)
{

    BMCWEB_ROUTE(
        app, "/redfish/v1/EventService/Actions/EventService.SubmitTestEvent/")
        .privileges(redfish::privileges::postEventService)
        .methods(boost::beast::http::verb::post)(
            [](const crow::Request&,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
                EventServiceManager::getInstance().sendTestEventLog();
                asyncResp->res.result(boost::beast::http::status::no_content);
            });
}

inline void requestRoutesEventDestinationCollection(App& app)
{
    BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions")
        .privileges(redfish::privileges::getEventDestinationCollection)
        .methods(boost::beast::http::verb::get)(
            [](const crow::Request&,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
                asyncResp->res.jsonValue = {
                    {"@odata.type",
                     "#EventDestinationCollection.EventDestinationCollection"},
                    {"@odata.id", "/redfish/v1/EventService/Subscriptions"},
                    {"Name", "Event Destination Collections"}};

                nlohmann::json& memberArray =
                    asyncResp->res.jsonValue["Members"];

                std::vector<std::string> subscripIds =
                    EventServiceManager::getInstance().getAllIDs();
                memberArray = nlohmann::json::array();
                asyncResp->res.jsonValue["Members@odata.count"] =
                    subscripIds.size();

                for (const std::string& id : subscripIds)
                {
                    memberArray.push_back(
                        {{"@odata.id",
                          "/redfish/v1/EventService/Subscriptions/" + id}});
                }
            });
    BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/")
        .privileges(redfish::privileges::postEventDestinationCollection)
        .methods(boost::beast::http::verb::post)(
            [](const crow::Request& req,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
                if (EventServiceManager::getInstance()
                        .getNumberOfSubscriptions() >= maxNoOfSubscriptions)
                {
                    messages::eventSubscriptionLimitExceeded(asyncResp->res);
                    return;
                }
                std::string destUrl;
                std::string protocol;
                std::optional<std::string> context;
                std::optional<std::string> subscriptionType;
                std::optional<std::string> eventFormatType2;
                std::optional<std::string> retryPolicy;
                std::optional<std::vector<std::string>> msgIds;
                std::optional<std::vector<std::string>> regPrefixes;
                std::optional<std::vector<std::string>> resTypes;
                std::optional<std::vector<nlohmann::json>> headers;
                std::optional<std::vector<nlohmann::json>> mrdJsonArray;

                if (!json_util::readJson(
                        req, asyncResp->res, "Destination", destUrl, "Context",
                        context, "Protocol", protocol, "SubscriptionType",
                        subscriptionType, "EventFormatType", eventFormatType2,
                        "HttpHeaders", headers, "RegistryPrefixes", regPrefixes,
                        "MessageIds", msgIds, "DeliveryRetryPolicy",
                        retryPolicy, "MetricReportDefinitions", mrdJsonArray,
                        "ResourceTypes", resTypes))
                {
                    return;
                }

                if (regPrefixes && msgIds)
                {
                    if (regPrefixes->size() && msgIds->size())
                    {
                        messages::mutualExclusiveProperties(
                            asyncResp->res, "RegistryPrefixes", "MessageIds");
                        return;
                    }
                }

                // Validate the URL using regex expression
                // Format: <protocol>://<host>:<port>/<uri>
                // protocol: http/https
                // host: Exclude ' ', ':', '#', '?'
                // port: Empty or numeric value with ':' separator.
                // uri: Start with '/' and Exclude '#', ' '
                //      Can include query params(ex: '/event?test=1')
                // TODO: Need to validate hostname extensively(as per rfc)
                const std::regex urlRegex(
                    "(http|https)://([^/\\x20\\x3f\\x23\\x3a]+):?([0-9]*)(/"
                    "([^\\x20\\x23\\x3f]*\\x3f?([^\\x20\\x23\\x3f])*)?)");
                std::cmatch match;
                if (!std::regex_match(destUrl.c_str(), match, urlRegex))
                {
                    messages::propertyValueFormatError(asyncResp->res, destUrl,
                                                       "Destination");
                    return;
                }

                std::string uriProto =
                    std::string(match[1].first, match[1].second);
                if (uriProto == "http")
                {
#ifndef BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING
                    messages::propertyValueFormatError(asyncResp->res, destUrl,
                                                       "Destination");
                    return;
#endif
                }

                std::string host = std::string(match[2].first, match[2].second);
                std::string port = std::string(match[3].first, match[3].second);
                std::string path = std::string(match[4].first, match[4].second);
                if (port.empty())
                {
                    if (uriProto == "http")
                    {
                        port = "80";
                    }
                    else
                    {
                        port = "443";
                    }
                }
                if (path.empty())
                {
                    path = "/";
                }

                std::shared_ptr<Subscription> subValue =
                    std::make_shared<Subscription>(host, port, path, uriProto);

                subValue->destinationUrl = destUrl;

                if (subscriptionType)
                {
                    if (*subscriptionType != "RedfishEvent")
                    {
                        messages::propertyValueNotInList(asyncResp->res,
                                                         *subscriptionType,
                                                         "SubscriptionType");
                        return;
                    }
                    subValue->subscriptionType = *subscriptionType;
                }
                else
                {
                    subValue->subscriptionType = "RedfishEvent"; // Default
                }

                if (protocol != "Redfish")
                {
                    messages::propertyValueNotInList(asyncResp->res, protocol,
                                                     "Protocol");
                    return;
                }
                subValue->protocol = protocol;

                if (eventFormatType2)
                {
                    if (std::find(supportedEvtFormatTypes.begin(),
                                  supportedEvtFormatTypes.end(),
                                  *eventFormatType2) ==
                        supportedEvtFormatTypes.end())
                    {
                        messages::propertyValueNotInList(asyncResp->res,
                                                         *eventFormatType2,
                                                         "EventFormatType");
                        return;
                    }
                    subValue->eventFormatType = *eventFormatType2;
                }
                else
                {
                    // If not specified, use default "Event"
                    subValue->eventFormatType = "Event";
                }

                if (context)
                {
                    subValue->customText = *context;
                }

                if (headers)
                {
                    subValue->httpHeaders = *headers;
                }

                if (regPrefixes)
                {
                    for (const std::string& it : *regPrefixes)
                    {
                        if (std::find(supportedRegPrefixes.begin(),
                                      supportedRegPrefixes.end(),
                                      it) == supportedRegPrefixes.end())
                        {
                            messages::propertyValueNotInList(
                                asyncResp->res, it, "RegistryPrefixes");
                            return;
                        }
                    }
                    subValue->registryPrefixes = *regPrefixes;
                }

                if (resTypes)
                {
                    for (const std::string& it : *resTypes)
                    {
                        if (std::find(supportedResourceTypes.begin(),
                                      supportedResourceTypes.end(),
                                      it) == supportedResourceTypes.end())
                        {
                            messages::propertyValueNotInList(asyncResp->res, it,
                                                             "ResourceTypes");
                            return;
                        }
                    }
                    subValue->resourceTypes = *resTypes;
                }

                if (msgIds)
                {
                    std::vector<std::string> registryPrefix;

                    // If no registry prefixes are mentioned, consider all
                    // supported prefixes
                    if (subValue->registryPrefixes.empty())
                    {
                        registryPrefix.assign(supportedRegPrefixes.begin(),
                                              supportedRegPrefixes.end());
                    }
                    else
                    {
                        registryPrefix = subValue->registryPrefixes;
                    }

                    for (const std::string& id : *msgIds)
                    {
                        bool validId = false;

                        // Check for Message ID in each of the selected Registry
                        for (const std::string& it : registryPrefix)
                        {
                            const boost::beast::span<
                                const redfish::message_registries::MessageEntry>
                                registry = redfish::message_registries::
                                    getRegistryFromPrefix(it);

                            if (std::any_of(
                                    registry.cbegin(), registry.cend(),
                                    [&id](const redfish::message_registries::
                                              MessageEntry& messageEntry) {
                                        return !id.compare(messageEntry.first);
                                    }))
                            {
                                validId = true;
                                break;
                            }
                        }

                        if (!validId)
                        {
                            messages::propertyValueNotInList(asyncResp->res, id,
                                                             "MessageIds");
                            return;
                        }
                    }

                    subValue->registryMsgIds = *msgIds;
                }

                if (retryPolicy)
                {
                    if (std::find(supportedRetryPolicies.begin(),
                                  supportedRetryPolicies.end(),
                                  *retryPolicy) == supportedRetryPolicies.end())
                    {
                        messages::propertyValueNotInList(asyncResp->res,
                                                         *retryPolicy,
                                                         "DeliveryRetryPolicy");
                        return;
                    }
                    subValue->retryPolicy = *retryPolicy;
                }
                else
                {
                    // Default "TerminateAfterRetries"
                    subValue->retryPolicy = "TerminateAfterRetries";
                }

                if (mrdJsonArray)
                {
                    for (nlohmann::json& mrdObj : *mrdJsonArray)
                    {
                        std::string mrdUri;
                        if (json_util::getValueFromJsonObject(
                                mrdObj, "@odata.id", mrdUri))
                        {
                            subValue->metricReportDefinitions.emplace_back(
                                mrdUri);
                        }
                        else
                        {
                            messages::propertyValueFormatError(
                                asyncResp->res,
                                mrdObj.dump(
                                    2, ' ', true,
                                    nlohmann::json::error_handler_t::replace),
                                "MetricReportDefinitions");
                            return;
                        }
                    }
                }

                std::string id =
                    EventServiceManager::getInstance().addSubscription(
                        subValue);
                if (id.empty())
                {
                    messages::internalError(asyncResp->res);
                    return;
                }

                messages::created(asyncResp->res);
                asyncResp->res.addHeader(
                    "Location", "/redfish/v1/EventService/Subscriptions/" + id);
            });
}

inline void requestRoutesEventDestination(App& app)
{
    BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
        .privileges(redfish::privileges::getEventDestination)
        .methods(boost::beast::http::verb::get)(
            [](const crow::Request&,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
               const std::string& param) {
                std::shared_ptr<Subscription> subValue =
                    EventServiceManager::getInstance().getSubscription(param);
                if (subValue == nullptr)
                {
                    asyncResp->res.result(
                        boost::beast::http::status::not_found);
                    return;
                }
                const std::string& id = param;

                asyncResp->res.jsonValue = {
                    {"@odata.type",
                     "#EventDestination.v1_7_0.EventDestination"},
                    {"Protocol", "Redfish"}};
                asyncResp->res.jsonValue["@odata.id"] =
                    "/redfish/v1/EventService/Subscriptions/" + id;
                asyncResp->res.jsonValue["Id"] = id;
                asyncResp->res.jsonValue["Name"] = "Event Destination " + id;
                asyncResp->res.jsonValue["Destination"] =
                    subValue->destinationUrl;
                asyncResp->res.jsonValue["Context"] = subValue->customText;
                asyncResp->res.jsonValue["SubscriptionType"] =
                    subValue->subscriptionType;
                asyncResp->res.jsonValue["HttpHeaders"] =
                    nlohmann::json::array();
                asyncResp->res.jsonValue["EventFormatType"] =
                    subValue->eventFormatType;
                asyncResp->res.jsonValue["RegistryPrefixes"] =
                    subValue->registryPrefixes;
                asyncResp->res.jsonValue["ResourceTypes"] =
                    subValue->resourceTypes;

                asyncResp->res.jsonValue["MessageIds"] =
                    subValue->registryMsgIds;
                asyncResp->res.jsonValue["DeliveryRetryPolicy"] =
                    subValue->retryPolicy;

                std::vector<nlohmann::json> mrdJsonArray;
                for (const auto& mdrUri : subValue->metricReportDefinitions)
                {
                    mrdJsonArray.push_back({{"@odata.id", mdrUri}});
                }
                asyncResp->res.jsonValue["MetricReportDefinitions"] =
                    mrdJsonArray;
            });
    BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
        // The below privilege is wrong, it should be ConfigureManager OR
        // ConfigureSelf
        // https://github.com/openbmc/bmcweb/issues/220
        //.privileges(redfish::privileges::patchEventDestination)
        .privileges({{"ConfigureManager"}})
        .methods(boost::beast::http::verb::patch)(
            [](const crow::Request& req,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
               const std::string& param) {
                std::shared_ptr<Subscription> subValue =
                    EventServiceManager::getInstance().getSubscription(param);
                if (subValue == nullptr)
                {
                    asyncResp->res.result(
                        boost::beast::http::status::not_found);
                    return;
                }

                std::optional<std::string> context;
                std::optional<std::string> retryPolicy;
                std::optional<std::vector<nlohmann::json>> headers;

                if (!json_util::readJson(req, asyncResp->res, "Context",
                                         context, "DeliveryRetryPolicy",
                                         retryPolicy, "HttpHeaders", headers))
                {
                    return;
                }

                if (context)
                {
                    subValue->customText = *context;
                }

                if (headers)
                {
                    subValue->httpHeaders = *headers;
                }

                if (retryPolicy)
                {
                    if (std::find(supportedRetryPolicies.begin(),
                                  supportedRetryPolicies.end(),
                                  *retryPolicy) == supportedRetryPolicies.end())
                    {
                        messages::propertyValueNotInList(asyncResp->res,
                                                         *retryPolicy,
                                                         "DeliveryRetryPolicy");
                        return;
                    }
                    subValue->retryPolicy = *retryPolicy;
                    subValue->updateRetryPolicy();
                }

                EventServiceManager::getInstance().updateSubscriptionData();
            });
    BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/")
        // The below privilege is wrong, it should be ConfigureManager OR
        // ConfigureSelf
        // https://github.com/openbmc/bmcweb/issues/220
        //.privileges(redfish::privileges::deleteEventDestination)
        .privileges({{"ConfigureManager"}})
        .methods(boost::beast::http::verb::delete_)(
            [](const crow::Request&,
               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
               const std::string& param) {
                if (!EventServiceManager::getInstance().isSubscriptionExist(
                        param))
                {
                    asyncResp->res.result(
                        boost::beast::http::status::not_found);
                    return;
                }
                EventServiceManager::getInstance().deleteSubscription(param);
            });
}

} // namespace redfish