/*.
// Copyright (c) 2018 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 "user_layer.hpp"

#include <boost/interprocess/sync/file_lock.hpp>
#include <boost/interprocess/sync/named_recursive_mutex.hpp>
#include <ipmid/api.hpp>
#include <sdbusplus/bus.hpp>

#include <cstdint>
#include <ctime>
#include <variant>

namespace ipmi
{

using DbusUserPropVariant =
    std::variant<std::vector<std::string>, std::string, bool>;

using DbusUserObjPath = sdbusplus::message::object_path;

using DbusUserObjProperties =
    std::vector<std::pair<std::string, DbusUserPropVariant>>;

using DbusUserObjValue = std::map<std::string, DbusUserObjProperties>;

/**
 * @enum User update events.
 */
enum class UserUpdateEvent
{
    reservedEvent,
    userCreated,
    userDeleted,
    userRenamed,
    userGrpUpdated,
    userPrivUpdated,
    userStateUpdated
};

/** @struct UserPrivAccess
 *
 *  Structure for user privilege access (refer spec sec 22.22)
 */
struct UserPrivAccess
{
    uint8_t privilege;
    bool ipmiEnabled;
    bool linkAuthEnabled;
    bool accessCallback;
};

/** @struct UserInfo
 *
 *  Structure for user related information
 */
struct UserInfo
{
    uint8_t userName[ipmiMaxUserName];
    UserPrivAccess userPrivAccess[ipmiMaxChannels];
    bool userEnabled;
    bool userInSystem;
    bool fixedUserName;
    PayloadAccess payloadAccess[ipmiMaxChannels];
};

/** @struct UsersTbl
 *
 *  Structure for array of user related information
 */
struct UsersTbl
{
    //+1 to map with UserId directly. UserId 0 is reserved.
    UserInfo user[ipmiMaxUsers + 1];
};

/** @brief PAM User Authentication check
 *
 *  @param[in] username - username in string
 *  @param[in] password	- password in string
 *
 *  @return status
 */
bool pamUserCheckAuthenticate(std::string_view username,
                              std::string_view password);

class UserAccess;

UserAccess& getUserAccessObject();

class UserAccess
{
  public:
    UserAccess(const UserAccess&) = delete;
    UserAccess& operator=(const UserAccess&) = delete;
    UserAccess(UserAccess&&) = delete;
    UserAccess& operator=(UserAccess&&) = delete;

    ~UserAccess();
    UserAccess();

    /** @brief determines valid channel
     *
     *  @param[in] chNum - channel number
     *
     *  @return true if valid, false otherwise
     */
    static bool isValidChannel(const uint8_t chNum);

    /** @brief determines valid userId
     *
     *  @param[in] userId - user id
     *
     *  @return true if valid, false otherwise
     */
    static bool isValidUserId(const uint8_t userId);

    /** @brief determines valid user privilege
     *
     *  @param[in] priv - Privilege
     *
     *  @return true if valid, false otherwise
     */
    static bool isValidPrivilege(const uint8_t priv);

    /** @brief determines sync index to be mapped with common-user-management
     *
     *  @return Index which will be used as sync index
     */
    static uint8_t getUsrMgmtSyncIndex();

    /** @brief Converts system privilege to IPMI privilege
     *
     *  @param[in] value - Privilege in string
     *
     *  @return CommandPrivilege - IPMI privilege type
     */
    static CommandPrivilege convertToIPMIPrivilege(const std::string& value);

    /** @brief Converts IPMI privilege to system privilege
     *
     *  @param[in] value - IPMI privilege
     *
     *  @return System privilege in string
     */
    static std::string convertToSystemPrivilege(const CommandPrivilege& value);

    /** @brief determines whether user name is valid
     *
     *  @param[in] userNameInChar - user name
     *
     *  @return true if valid, false otherwise
     */
    bool isValidUserName(const std::string& userName);

    /** @brief determines whether ipmi is in available groups list
     *
     * @return true if ipmi group is present, false otherwise
     */
    bool isIpmiInAvailableGroupList();

    /** @brief provides user id of the user
     *
     *  @param[in] userName - user name
     *
     *  @return user id of the user, else invalid user id (0xFF), if user not
     * found
     */
    uint8_t getUserId(const std::string& userName);

    /** @brief provides user information
     *
     *  @param[in] userId - user id
     *
     *  @return UserInfo for the specified user id
     */
    UserInfo* getUserInfo(const uint8_t userId);

    /** @brief sets user information
     *
     *  @param[in] userId - user id
     *  @param[in] userInfo - user information
     *
     */
    void setUserInfo(const uint8_t userId, UserInfo* userInfo);

    /** @brief provides user name
     *
     *  @param[in] userId - user id
     *  @param[out] userName - user name
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc getUserName(const uint8_t userId, std::string& userName);

    /** @brief to set user name
     *
     *  @param[in] userId - user id
     *  @param[in] userName - user name
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc setUserName(const uint8_t userId, const std::string& userName);

    /** @brief to set user enabled state
     *
     *  @param[in] userId - user id
     *  @param[in] enabledState - enabled state of the user
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc setUserEnabledState(const uint8_t userId, const bool& enabledState);

    /** @brief to set user password
     *
     *  @param[in] userId - user id
     *  @param[in] userPassword  - new password of the user
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc setUserPassword(const uint8_t userId, const char* userPassword);

    /** @brief to set special user password
     *
     *  @param[in] userName - user name
     *  @param[in] userPassword  - new password of the user
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc setSpecialUserPassword(const std::string& userName,
                              const SecureString& userPassword);

    /** @brief to set user privilege and access details
     *
     *  @param[in] userId - user id
     *  @param[in] chNum - channel number
     *  @param[in] privAccess - privilege access
     *  @param[in] otherPrivUpdates - other privilege update flag to update ipmi
     * enable, link authentication and access callback
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
                              const UserPrivAccess& privAccess,
                              const bool& otherPrivUpdates);

    /** @brief to get user payload access details from userInfo entry.
     *
     *  @param[in] userInfo    - userInfo entry in usersTbl.
     *  @param[out] stdPayload - stdPayloadEnables1 in a 2D-array.
     *  @param[out] oemPayload - oemPayloadEnables1 in a 2D-array.
     *
     *  @details Update the given 2D-arrays using the payload access details
     *  available in the given userInfo entry (from usersTbl).
     *  This 2D-array will be mapped to a JSON object (which will be written to
     *  a JSON file subsequently).
     */
    void readPayloadAccessFromUserInfo(
        const UserInfo& userInfo,
        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
            stdPayload,
        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
            oemPayload);

    /** @brief to update user payload access details in userInfo entry.
     *
     *  @param[in] stdPayload - stdPayloadEnables1 in a 2D-array.
     *  @param[in] oemPayload - oemPayloadEnables1 in a 2D-array.
     *  @param[out] userInfo  - userInfo entry in usersTbl.
     *
     *  @details Update user payload access details of a given userInfo
     *  entry (in usersTbl) with the information provided in given 2D-arrays.
     *  This 2D-array was created out of a JSON object (which was created by
     *  parsing a JSON file).
     */
    void updatePayloadAccessInUserInfo(
        const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
            stdPayload,
        const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
            oemPayload,
        UserInfo& userInfo);

    /** @brief to set user payload access details
     *
     *  @param[in] chNum - channel number
     *  @param[in] operation - Enable / Disable
     *  @param[in] userId - user id
     *  @param[in] payloadAccess - payload access
     *
     *  @return ccSuccess for success, others for failure.
     */
    Cc setUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
                            const uint8_t userId,
                            const PayloadAccess& payloadAccess);

    /** @brief reads user management related data from configuration file
     *
     */
    void readUserData();

    /** @brief writes user management related data to configuration file
     *
     */
    void writeUserData();

    /** @brief Funtion which checks and reload configuration file data if
     * needed.
     *
     */
    void checkAndReloadUserData();

    /** @brief provides user details from D-Bus user property data
     *
     *  @param[in] properties - D-Bus user property
     *  @param[out] usrGrps - user group details
     *  @param[out] usrPriv - user privilege
     *  @param[out] usrEnabled - enabled state of the user.
     *
     *  @return 0 for success, -errno for failure.
     */
    void getUserProperties(const DbusUserObjProperties& properties,
                           std::vector<std::string>& usrGrps,
                           std::string& usrPriv, bool& usrEnabled);

    /** @brief provides user details from D-Bus user object data
     *
     *  @param[in] userObjs - D-Bus user object
     *  @param[out] usrGrps - user group details
     *  @param[out] usrPriv - user privilege
     *  @param[out] usrEnabled - enabled state of the user.
     *
     *  @return 0 for success, -errno for failure.
     */
    int getUserObjProperties(const DbusUserObjValue& userObjs,
                             std::vector<std::string>& usrGrps,
                             std::string& usrPriv, bool& usrEnabled);

    /** @brief function to add user entry information to the configuration
     *
     *  @param[in] userName - user name
     *  @param[in] priv - privilege of the user
     *  @param[in] enabled - enabled state of the user
     *
     *  @return true for success, false for failure
     */
    bool addUserEntry(const std::string& userName, const std::string& priv,
                      const bool& enabled);

    /** @brief function to delete user entry based on user index
     *
     *  @param[in] usrIdx - user index
     *
     */
    void deleteUserIndex(const size_t& usrIdx);

    /** @brief function to get users table
     *
     */
    UsersTbl* getUsersTblPtr();

    std::unique_ptr<boost::interprocess::named_recursive_mutex> userMutex{
        nullptr};

  private:
    UsersTbl usersTbl;
    std::vector<std::string> availablePrivileges;
    std::vector<std::string> availableGroups;
    sdbusplus::bus_t bus;
    std::timespec fileLastUpdatedTime;
    bool signalHndlrObject = false;
    boost::interprocess::file_lock sigHndlrLock;
    boost::interprocess::file_lock mutexCleanupLock;

    /** @brief function to get user configuration file timestamp
     *
     *  @return time stamp or -EIO for failure
     */
    std::timespec getUpdatedFileTime();

    /** @brief function to available system privileges and groups
     *
     */
    void getSystemPrivAndGroups();

    /** @brief function to init user data from configuration & D-Bus objects
     * and to register for signals
     *
     */
    void cacheUserDataFile();
};

} // namespace ipmi