/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include <aws/core/config/AWSProfileConfigLoader.h>
#include <aws/core/utils/memory/stl/AWSSet.h>
#include <aws/core/utils/memory/stl/AWSStreamFwd.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/logging/LogMacros.h>
#include <fstream>

namespace Aws
{
    namespace Config
    {
        using namespace Aws::Utils;
        using namespace Aws::Auth;

        static const char REGION_KEY[]                       = "region";
        static const char ACCESS_KEY_ID_KEY[]                = "aws_access_key_id";
        static const char SECRET_KEY_KEY[]                   = "aws_secret_access_key";
        static const char SESSION_TOKEN_KEY[]                = "aws_session_token";
        static const char SSO_START_URL_KEY[]                = "sso_start_url";
        static const char SSO_REGION_KEY[]                   = "sso_region";
        static const char SSO_ACCOUNT_ID_KEY[]               = "sso_account_id";
        static const char SSO_ROLE_NAME_KEY[]                = "sso_role_name";
        static const char SSO_SESSION_KEY[]                  = "sso_session";
        static const char ROLE_ARN_KEY[]                     = "role_arn";
        static const char EXTERNAL_ID_KEY[]                  = "external_id";
        static const char CREDENTIAL_PROCESS_COMMAND[]       = "credential_process";
        static const char SOURCE_PROFILE_KEY[]               = "source_profile";
        static const char PROFILE_SECTION[]                  = "profile";
        static const char DEFAULT[]                          = "default";
        static const char SSO_SESSION_SECTION[]              = "sso-session";
        static const char DEFAULTS_MODE_KEY[]                = "defaults_mode";
        static const char EQ                                 = '=';
        static const char LEFT_BRACKET                       = '[';
        static const char RIGHT_BRACKET                      = ']';
        static const char PARSER_TAG[]                       = "Aws::Config::ConfigFileProfileFSM";

        // generated by python from identifier regex pattern from the spec: R"([A-Za-z0-9_\-/.%@:\+]+)":
        // #py: ''.join(chr(i) for i in range(128) if re.match("[A-Za-z0-9_\-\/.%@:\+]", chr(i)))
        const char IDENTIFIER_ALLOWED_CHARACTERS[] = R"(%+-./0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz)";
        static const size_t IDENTIFIER_ALLOWED_CHARACTERS_SZ = sizeof(IDENTIFIER_ALLOWED_CHARACTERS) - 1;
        const char WHITESPACE_CHARACTERS[]         = "\t ";
        static const size_t WHITESPACE_CHARACTERS_SZ = sizeof(WHITESPACE_CHARACTERS) - 1;
        const char COMMENT_START[]                 = "#;";
        static const size_t COMMENT_START_SZ = sizeof(COMMENT_START) - 1;

        struct ProfilePropertyAccessFunctions
        {
            const char* PropertyKey;
            std::function<void(Profile&, const Aws::String&)> Setter;
            std::function<const Aws::String&(const Profile&)> Getter;
        };

        static const ProfilePropertyAccessFunctions PROFILE_PROPERTY_FUNCS[] =
                {{REGION_KEY, &Profile::SetRegion, &Profile::GetRegion},
                        //ACCESS_KEY_ID_KEY, - AwsCredentials require special handling
                        //SECRET_KEY_KEY,
                        //SESSION_TOKEN_KEY,
                 {SSO_START_URL_KEY, &Profile::SetSsoStartUrl, &Profile::GetSsoStartUrl},
                 {SSO_REGION_KEY, &Profile::SetSsoRegion, &Profile::GetSsoRegion},
                 {SSO_ACCOUNT_ID_KEY, &Profile::SetSsoAccountId, &Profile::GetSsoAccountId},
                 {SSO_ROLE_NAME_KEY, &Profile::SetSsoRoleName, &Profile::GetSsoRoleName},
                        //SSO_SESSION_KEY - SsoSession requires special handling
                 {ROLE_ARN_KEY, &Profile::SetRoleArn, &Profile::GetRoleArn},
                 {EXTERNAL_ID_KEY, &Profile::SetExternalId, &Profile::GetExternalId},
                 {CREDENTIAL_PROCESS_COMMAND, &Profile::SetCredentialProcess, &Profile::GetCredentialProcess},
                 {SOURCE_PROFILE_KEY, &Profile::SetSourceProfile, &Profile::GetSourceProfile},
                 {DEFAULTS_MODE_KEY, &Profile::SetDefaultsMode, &Profile::GetDefaultsMode}};

        template<typename EntryT, size_t N>
        const EntryT* FindInStaticArray(const EntryT (&array)[N], const Aws::String& searchKey)
        {
            const EntryT* found = std::find_if(array, array + N,
                                              [&searchKey](const EntryT& entry)
                                              {
                                                  return searchKey == entry.PropertyKey;
                                              });

            if(!!found && found != array + N)
                return found;

            return nullptr;
        }

        static const char* PROFILE_KEY_SPECIAL_HANDLING[] =
                {ACCESS_KEY_ID_KEY, SECRET_KEY_KEY, SESSION_TOKEN_KEY, SSO_SESSION_KEY};
        static const size_t PROFILE_KEY_SPECIAL_HANDLING_SZ = sizeof(PROFILE_KEY_SPECIAL_HANDLING) / sizeof(PROFILE_KEY_SPECIAL_HANDLING[0]);

        struct SsoSessionPropertyAccessFunctions
        {
            const char* PropertyKey;
            std::function<void(Profile::SsoSession&, const Aws::String&)> Setter;
            std::function<const Aws::String&(const Profile::SsoSession&)> Getter;
        };
        static const SsoSessionPropertyAccessFunctions SSO_SESSION_PROPERTY_FUNCS[] =
                {{SSO_REGION_KEY, &Profile::SsoSession::SetSsoRegion, &Profile::SsoSession::GetSsoRegion},
                 {SSO_START_URL_KEY, &Profile::SsoSession::SetSsoStartUrl, &Profile::SsoSession::GetSsoStartUrl}};

        class ConfigFileProfileFSM
        {
        public:
            ConfigFileProfileFSM(bool useProfilePrefix)
            : m_useProfilePrefix(useProfilePrefix)
            {}

            const Aws::Map<String, Profile>& GetProfiles() const { return m_foundProfiles; }

            void ParseStream(Aws::IStream& stream)
            {
                static const size_t ASSUME_EMPTY_LEN = 3;
                State currentState = START;
                Aws::String currentSectionName;
                Aws::Map<Aws::String, Aws::String> currentKeyValues;

                Aws::String rawLine;
                while(std::getline(stream, rawLine) && currentState != FAILURE)
                {
                    Aws::String line = rawLine.substr(0, rawLine.find_first_of(COMMENT_START)); // ignore comments
                    if (line.empty() || line.length() < ASSUME_EMPTY_LEN || line.find_first_not_of(WHITESPACE_CHARACTERS) == Aws::String::npos)
                    {
                        continue;
                    }

                    auto openPos = line.find(LEFT_BRACKET);
                    auto closePos = line.find(RIGHT_BRACKET);

                    if(openPos != std::string::npos && closePos != std::string::npos)
                    {
                        FlushSection(currentState, currentSectionName, currentKeyValues);
                        currentKeyValues.clear();
                        ParseSectionDeclaration(line, currentSectionName, currentState);
                        continue;
                    }

                    if(PROFILE_FOUND == currentState || SSO_SESSION_FOUND == currentState)
                    {
                        auto equalsPos = line.find(EQ);
                        if (equalsPos != std::string::npos)
                        {
                            auto key = StringUtils::Trim(line.substr(0, equalsPos).c_str());
                            auto value = StringUtils::Trim(line.substr(equalsPos + 1).c_str());
                            currentKeyValues[key] = value;
                            continue;
                        }
                    }

                    if(UNKNOWN_SECTION_FOUND == currentState)
                    {
                        // skip any unknown sections
                        continue;
                    }

                    AWS_LOGSTREAM_ERROR(PARSER_TAG, "Unexpected line in the aws shared profile: " << rawLine);
                    currentState = FAILURE;
                    break;
                }

                FlushSection(currentState, currentSectionName, currentKeyValues);

                // Put sso-sessions into profiles
                for(auto& profile : m_foundProfiles)
                {
                    const Aws::String& profileSsoSessionName = profile.second.GetValue(SSO_SESSION_KEY);
                    if(!profileSsoSessionName.empty())
                    {
                        auto ssoSessionIt = m_foundSsoSessions.find(profileSsoSessionName);
                        if(ssoSessionIt == m_foundSsoSessions.end())
                        {
                            AWS_LOGSTREAM_ERROR(PARSER_TAG, "AWS profile has reference to a missing sso_session: " << profileSsoSessionName);
                            currentState = FAILURE;
                            continue;
                        }
                        auto ssoSession = ssoSessionIt->second;
                        auto prof = profile.second;
                        // If sso session and profile have conflicting start url or region, fail to parse
                        // the session/sso specific profile properties
                        auto hasConflictingStartUrls = !ssoSession.GetSsoStartUrl().empty()
                                                       && !prof.GetSsoStartUrl().empty()
                                                       && ssoSession.GetSsoStartUrl() != prof.GetSsoStartUrl();
                        auto hasConflictingRegions = !ssoSession.GetSsoRegion().empty()
                                                      && !prof.GetSsoRegion().empty()
                                                      && ssoSession.GetSsoRegion() != prof.GetSsoRegion();
                        if (hasConflictingStartUrls || hasConflictingRegions) {
                            AWS_LOGSTREAM_ERROR(PARSER_TAG,
                                "SSO profile has a start url or region conflict with sso session");
                            prof.SetSsoStartUrl("");
                            prof.SetSsoRegion("");
                            prof.SetSsoAccountId("");
                            prof.SetSsoRoleName("");
                            continue;
                        }
                        profile.second.SetSsoSession(ssoSessionIt->second);
                    }
                }

                if(FAILURE == currentState)
                {
                    AWS_LOGSTREAM_ERROR(PARSER_TAG, "AWS shared profile config parsing failed");
                }
            }

        private:
            // true means Shared Config parsing, false means Shared Credentials parsing
            bool m_useProfilePrefix = false;

            enum State
            {
                START = 0,
                PROFILE_FOUND,
                SSO_SESSION_FOUND,
                UNKNOWN_SECTION_FOUND,
                FAILURE
            };

            /**
             * Helper function to parse a single word (aka section identifier) containing allowed characters from a line and a pos
             *   i.e. line="[ profile default ]";identifierBegin=10  will return "default"
             * @param line, a section definition line being parsed
             * @param identifierBegin, an Aws::String position to start parsing
             * @param oErrorMsg, a reference to Aws::String to store error message in case of a parsing error.
             * @return Aws::String, e.g. "default"
             */
            Aws::String ParseIdentifier(const Aws::String& line, Aws::String::size_type identifierBegin, Aws::String& oErrorMsg)
            {
                // pos at the beginning of section Identifier (or sso_session section keyword)
                Aws::String::size_type identifierLength = 0;
                Aws::String::size_type pos = identifierBegin;
                while(pos < line.length())
                {
                    if(std::find(IDENTIFIER_ALLOWED_CHARACTERS,
                                 IDENTIFIER_ALLOWED_CHARACTERS + IDENTIFIER_ALLOWED_CHARACTERS_SZ,
                                 line[pos]) != IDENTIFIER_ALLOWED_CHARACTERS + IDENTIFIER_ALLOWED_CHARACTERS_SZ)
                    {
                        identifierLength++;
                        pos++;
                    }
                    else
                    {
                        break;
                    }
                }
                const Aws::String SECTION_END_CHARS_TO_SKIP = Aws::String(WHITESPACE_CHARACTERS) + RIGHT_BRACKET;

                if(identifierLength == 0)
                {
                    oErrorMsg = "identifier is missing";
                    return "";
                }
                if(pos >= line.size() || SECTION_END_CHARS_TO_SKIP.find(line[pos]) == Aws::String::npos) {
                    oErrorMsg = "a blank space character or closing bracket is expected after Identifier";
                    return "";
                }
                Aws::String sectionIdentifier = line.substr(identifierBegin, identifierLength);

                return sectionIdentifier;
            }

            /**
             * A helper function to parse config section declaration line
             * @param line, an input line, e.g. "[profile default]"
             * @param ioSectionName, a return argument representing parsed section Identifier, e.g. "default"
             * @param ioState, a return argument representing parser state, e.g. PROFILE_FOUND
             */
            void ParseSectionDeclaration(const Aws::String& line,
                                         Aws::String& ioSectionName,
                                         State& ioState)
            {
                do { // goto in a form of "do { break; } while(0);"
                    Aws::String::size_type pos = 0;
                    pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
                    if(pos != Aws::String::npos && LEFT_BRACKET != line[pos])
                    {
                        AWS_LOGSTREAM_ERROR(PARSER_TAG, "First non-blank space character of a section definition must be [, line:" << line);
                        break;
                    }
                    pos++;
                    pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
                    if(pos == Aws::String::npos || pos >= line.size())
                    {
                        AWS_LOGSTREAM_ERROR(PARSER_TAG, "Unknown section found in the aws config file: " << line);
                        break;
                    }
                    bool defaultProfileOrSsoSectionRequired = false;
                    if (m_useProfilePrefix)
                    {
                        // in configuration files, the profile name must start with profile. (eg. [profile profile-name]),
                        // except where the profile name is default. When the profile name is default it may start with profile
                        static const size_t PROFILE_KEYWORD_LENGTH = 7;
                        if(line.rfind(PROFILE_SECTION, pos + PROFILE_KEYWORD_LENGTH) != Aws::String::npos)
                        {
                            // skipping required (optional for default) profile keyword
                            pos += PROFILE_KEYWORD_LENGTH;
                            if(pos >= line.size() ||
                               std::find(WHITESPACE_CHARACTERS,
                                         WHITESPACE_CHARACTERS + WHITESPACE_CHARACTERS_SZ,
                                         line[pos]) == WHITESPACE_CHARACTERS + WHITESPACE_CHARACTERS_SZ)
                            {
                                AWS_LOGSTREAM_ERROR(PARSER_TAG, "Expected a blank space after \"profile\" keyword: " << line);
                                break;
                            }
                            pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
                        }
                        else
                        {
                            defaultProfileOrSsoSectionRequired = true;
                        }
                    }

                    Aws::String errorMsg;
                    Aws::String sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
                    if (!errorMsg.empty())
                    {
                        AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse section identifier: " << errorMsg << " " << line);
                        break;
                    }
                    pos += sectionIdentifier.length();

                    if(defaultProfileOrSsoSectionRequired)
                    {
                        if (sectionIdentifier != DEFAULT && sectionIdentifier != SSO_SESSION_SECTION)
                        {
                            AWS_LOGSTREAM_ERROR(PARSER_TAG, "In configuration files, the profile name must start with "
                                                            "profile keyword (except default profile): " << line);
                            break;
                        }
                        if (sectionIdentifier != SSO_SESSION_SECTION)
                        {
                            // profile found, still pending check for closing bracket
                            ioState = PROFILE_FOUND;
                            ioSectionName = sectionIdentifier;
                        }
                    }

                    if(!m_useProfilePrefix || sectionIdentifier != SSO_SESSION_SECTION)
                    {
                        // profile found, still pending check for closing bracket
                        ioState = PROFILE_FOUND;
                        ioSectionName = sectionIdentifier;
                    }

                    if(m_useProfilePrefix && sectionIdentifier == SSO_SESSION_SECTION)
                    {
                        // "[sso_session..." found, continue parsing for sso_session identifier
                        pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
                        if(pos == Aws::String::npos)
                        {
                            AWS_LOGSTREAM_ERROR(PARSER_TAG, "Expected a blank space after \"sso_session\" keyword: " << line);
                            break;
                        }

                        sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
                        if (!errorMsg.empty())
                        {
                            AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse section identifier: " << errorMsg << " " << line);
                            break;
                        }
                        pos += sectionIdentifier.length();
                        // sso_session found, still pending check for closing bracket
                        ioState = SSO_SESSION_FOUND;
                        ioSectionName = sectionIdentifier;
                    }

                    pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
                    if(pos == Aws::String::npos)
                    {
                        AWS_LOGSTREAM_ERROR(PARSER_TAG, "Expected a non-blank space after section identifier (i.e. missing \"]\"): " << line);
                        break;
                    }
                    if(line[pos] != RIGHT_BRACKET)
                    {
                        AWS_LOGSTREAM_ERROR(PARSER_TAG, "Missing closing bracket after Section Identifier "
                                                        "(i.e. missing \"]\" or extra non-blank characters before \"]\"): " << line);
                        break;
                    }
                    pos++;
                    pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
                    if(pos != Aws::String::npos &&
                       std::find(COMMENT_START, COMMENT_START + COMMENT_START_SZ, line[pos]) == COMMENT_START + COMMENT_START_SZ)
                    {
                        AWS_LOGSTREAM_ERROR(PARSER_TAG, "Found unexpected characters after closing bracket of Section Identifier " << line);
                        break;
                    }
                    // the rest is a comment, and we don't care about it.
                    if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND) || ioSectionName.empty())
                    {
                        AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unexpected parser state after attempting to parse section " << line);
                        break;
                    }
                    return;
                } while(0); // end of goto in a form of "do { break; } while(0);"

                ioSectionName.erase();
                ioState = UNKNOWN_SECTION_FOUND;
                return;
            }

            /**
             * A helper function to store currently being parsed section along with its properties
             *   (i.e. [profile default] and its key1=val1 under).
             * @param currentState, a current parser State, e.g. PROFILE_FOUND
             * @param currentSectionName, a current section identifier, e.g. "default"
             * @param currentKeyValues, a map of parsed key-value properties of a section definition being recorded
             */
            void FlushSection(const State currentState, const Aws::String& currentSectionName, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
            {
                if(START == currentState || currentSectionName.empty())
                {
                    return; //nothing to flush
                }

                if(PROFILE_FOUND == currentState)
                {
                    Profile& profile = m_foundProfiles[currentSectionName];

                    for(const auto& keyVal : currentKeyValues)
                    {
                        auto setterFuncPtr = FindInStaticArray(PROFILE_PROPERTY_FUNCS, keyVal.first);
                        if(setterFuncPtr)
                        {
                            AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Found " << setterFuncPtr->PropertyKey << " " << keyVal.second);
                            setterFuncPtr->Setter(profile, keyVal.second);
                        }
                        else
                        {
                            auto specialPropertyKey = std::find_if(PROFILE_KEY_SPECIAL_HANDLING, PROFILE_KEY_SPECIAL_HANDLING + PROFILE_KEY_SPECIAL_HANDLING_SZ,
                                         [&keyVal](const char* entry)
                                         {
                                             return !!entry && keyVal.first == entry;
                                         });

                            if (specialPropertyKey && specialPropertyKey != PROFILE_KEY_SPECIAL_HANDLING + PROFILE_KEY_SPECIAL_HANDLING_SZ)
                            {
                                AWS_LOGSTREAM_INFO(PARSER_TAG, "Unknown property: " << keyVal.first << " in the profile: " << currentSectionName);
                            }
                        }
                    }

                    auto accessKeyIdIter = currentKeyValues.find(ACCESS_KEY_ID_KEY);
                    Aws::String accessKey, secretKey, sessionToken;
                    if (accessKeyIdIter != currentKeyValues.end())
                    {
                        accessKey = accessKeyIdIter->second;
                        AWS_LOGSTREAM_DEBUG(PARSER_TAG, "found access key " << accessKey);

                        auto secretAccessKeyIter = currentKeyValues.find(SECRET_KEY_KEY);
                        auto sessionTokenIter = currentKeyValues.find(SESSION_TOKEN_KEY);
                        if (secretAccessKeyIter != currentKeyValues.end())
                        {
                            secretKey = secretAccessKeyIter->second;
                        }
                        else
                        {
                            AWS_LOGSTREAM_ERROR(PARSER_TAG, "No secret access key found even though an access key was specified. This will cause all signed AWS calls to fail.");
                        }

                        if (sessionTokenIter != currentKeyValues.end())
                        {
                            sessionToken = sessionTokenIter->second;
                        }

                        profile.SetCredentials(Aws::Auth::AWSCredentials(accessKey, secretKey, sessionToken));
                    }

                    if (!profile.GetSsoStartUrl().empty() || !profile.GetSsoRegion().empty()
                        || !profile.GetSsoAccountId().empty() || !profile.GetSsoRoleName().empty())
                    {
                        // If there is no sso session, all fields are required. If an SSO session is present,
                        // then only account id and sso role name are required.
                        auto hasSession = currentKeyValues.find(SSO_SESSION_KEY) != currentKeyValues.end();
                        auto hasInvalidProfileWithoutSession = !hasSession &&
                                                               (profile.GetSsoStartUrl().empty()
                                                                || profile.GetSsoRegion().empty()
                                                                || profile.GetSsoAccountId().empty()
                                                                || profile.GetSsoRoleName().empty());
                        auto hasInvalidProfileWithSession = hasSession &&
                                                            (profile.GetSsoAccountId().empty()
                                                             || profile.GetSsoRoleName().empty());
                        if (hasInvalidProfileWithoutSession || hasInvalidProfileWithSession) {
                            profile.SetSsoStartUrl("");
                            profile.SetSsoRegion("");
                            profile.SetSsoAccountId("");
                            profile.SetSsoRoleName("");
                            AWS_LOGSTREAM_ERROR(PARSER_TAG, "invalid SSO configuration for aws profile " << currentSectionName);
                        }
                    }

                    profile.SetName(currentSectionName);
                    profile.SetAllKeyValPairs(std::move(currentKeyValues));
                }
                else if (SSO_SESSION_FOUND == currentState) {
                    Profile::SsoSession& ssoSession = m_foundSsoSessions[currentSectionName];
                    for(const auto& keyVal : currentKeyValues)
                    {
                        auto setterFuncPtr = FindInStaticArray(SSO_SESSION_PROPERTY_FUNCS, keyVal.first);
                        if(setterFuncPtr)
                        {
                            AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Found sso-session property " << setterFuncPtr->PropertyKey << " " << keyVal.second);
                            setterFuncPtr->Setter(ssoSession, keyVal.second);
                        }
                        else
                        {
                            AWS_LOGSTREAM_INFO(PARSER_TAG, "Unknown property: " << keyVal.first << " in the sso-session: " << currentSectionName);
                        }
                    }
                    ssoSession.SetName(currentSectionName);
                    ssoSession.SetAllKeyValPairs(std::move(currentKeyValues));
                }
                else
                {
                    AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unknown parser error: unexpected state " << currentState);
                }
            }

            Aws::Map<String, Profile> m_foundProfiles;
            Aws::Map<String, Profile::SsoSession> m_foundSsoSessions;
        };

        static const char* const CONFIG_FILE_LOADER = "Aws::Config::AWSConfigFileProfileConfigLoader";

        AWSConfigFileProfileConfigLoader::AWSConfigFileProfileConfigLoader(const Aws::String& fileName, bool useProfilePrefix) :
                m_fileName(fileName), m_useProfilePrefix(useProfilePrefix)
        {
            AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Initializing config loader against fileName "
                    << fileName << " and using profilePrefix = " << useProfilePrefix);
        }

        bool AWSConfigFileProfileConfigLoader::LoadInternal()
        {
            m_profiles.clear();

            Aws::IFStream inputFile(m_fileName.c_str());
            if(inputFile)
            {
                ConfigFileProfileFSM parser(m_useProfilePrefix);
                parser.ParseStream(inputFile);
                m_profiles = parser.GetProfiles();
                return m_profiles.size() > 0;
            }

            AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Unable to open config file " << m_fileName << " for reading.");

            return false;
        }

        bool AWSConfigFileProfileConfigLoader::PersistInternal(const Aws::Map<Aws::String, Profile>& profiles)
        {
            Aws::OFStream outputFile(m_fileName.c_str(), std::ios_base::out | std::ios_base::trunc);
            if(outputFile)
            {
                Aws::UnorderedMap<Aws::String, std::reference_wrapper<const Profile::SsoSession>> ssoSessionsToDump;

                for(const auto& profile : profiles)
                {
                    Aws::String prefix = m_useProfilePrefix ? PROFILE_SECTION : "";

                    AWS_LOGSTREAM_DEBUG(CONFIG_FILE_LOADER, "Writing profile " << profile.first << " to disk.");

                    outputFile << LEFT_BRACKET << prefix << " " << profile.second.GetName() << RIGHT_BRACKET << std::endl;
                    const Aws::Auth::AWSCredentials& credentials = profile.second.GetCredentials();
                    if (!credentials.GetAWSAccessKeyId().empty()) {
                        outputFile << ACCESS_KEY_ID_KEY << EQ << credentials.GetAWSAccessKeyId() << std::endl;
                    }
                    if (!credentials.GetAWSSecretKey().empty()) {
                        outputFile << SECRET_KEY_KEY << EQ << credentials.GetAWSSecretKey() << std::endl;
                    }
                    if(!credentials.GetSessionToken().empty()) {
                        outputFile << SESSION_TOKEN_KEY << EQ << credentials.GetSessionToken() << std::endl;
                    }
                    // credentials.GetExpiration().Millis() <- is not present in a config.

                    for(const auto& profilePropertyPair : PROFILE_PROPERTY_FUNCS)
                    {
                        const auto& profilePropertyValue = profilePropertyPair.Getter(profile.second);
                        if(!profilePropertyValue.empty())
                        {
                            outputFile << profilePropertyPair.PropertyKey << EQ << profilePropertyValue << std::endl;
                        }
                    }

                    if(profile.second.IsSsoSessionSet())
                    {
                        const auto& ssoSession = profile.second.GetSsoSession();
                        const auto alreadyScheduledForDumpIt = ssoSessionsToDump.find(ssoSession.GetName());
                        if (alreadyScheduledForDumpIt != ssoSessionsToDump.end() &&
                                alreadyScheduledForDumpIt->second.get() != ssoSession)
                        {
                            AWS_LOGSTREAM_WARN(CONFIG_FILE_LOADER, "2 or more profiles reference 'sso-session' section "
                                                                   "with the same name but different properties: " << ssoSession.GetName());
                        }
                        else
                        {
                            ssoSessionsToDump.insert({ssoSession.GetName(), std::cref(ssoSession)});
                        }
                        outputFile << SSO_SESSION_KEY << EQ << ssoSession.GetName() << std::endl;
                    }
                    outputFile << std::endl;
                }

                for(const auto& ssoSessionPair : ssoSessionsToDump)
                {
                    AWS_LOGSTREAM_DEBUG(CONFIG_FILE_LOADER, "Writing sso-session " << ssoSessionPair.first << " to disk.");
                    const Profile::SsoSession& ssoSession = ssoSessionPair.second.get();
                    outputFile << LEFT_BRACKET << SSO_SESSION_SECTION << " " << ssoSession.GetName() << RIGHT_BRACKET << std::endl;
                    for(const auto& ssoSessionPropertyPair : SSO_SESSION_PROPERTY_FUNCS)
                    {
                        const auto& profilePropertyValue = ssoSessionPropertyPair.Getter(ssoSession);
                        if(!profilePropertyValue.empty())
                        {
                            outputFile << ssoSessionPropertyPair.PropertyKey << EQ << profilePropertyValue << std::endl;
                        }
                    }
                    outputFile << std::endl;
                }

                AWS_LOGSTREAM_INFO(CONFIG_FILE_LOADER, "Profiles written to config file " << m_fileName);

                return true;
            }

            AWS_LOGSTREAM_WARN(CONFIG_FILE_LOADER, "Unable to open config file " << m_fileName << " for writing.");

            return false;
        }
    } // Config namespace
} // Aws namespace