/***********************************************************************************************************************************
S3 Storage Helper
***********************************************************************************************************************************/
#include <build.h>

#include <stdlib.h>

#include "common/debug.h"
#include "common/io/http/url.h"
#include "common/io/io.h"
#include "common/log.h"
#include "config/config.h"
#include "storage/helper.h"
#include "storage/posix/storage.h"
#include "storage/s3/helper.h"

/**********************************************************************************************************************************/
FN_EXTERN Storage *
storageS3Helper(const unsigned int repoIdx, const bool write, StoragePathExpressionCallback pathExpressionCallback)
{
    FUNCTION_LOG_BEGIN(logLevelDebug);
        FUNCTION_LOG_PARAM(UINT, repoIdx);
        FUNCTION_LOG_PARAM(BOOL, write);
        FUNCTION_LOG_PARAM_P(VOID, pathExpressionCallback);
    FUNCTION_LOG_END();

    ASSERT(cfgOptionIdxStrId(cfgOptRepoType, repoIdx) == STORAGE_S3_TYPE);

    Storage *result = NULL;

    MEM_CONTEXT_TEMP_BEGIN()
    {
        // Parse the endpoint url
        const HttpUrl *const url = httpUrlNewParseP(
            cfgOptionIdxStr(cfgOptRepoS3Endpoint, repoIdx), .type = httpProtocolTypeAny, .defaultType = httpProtocolTypeHttps);
        const String *const endPoint = httpUrlHost(url);
        unsigned int port = httpUrlPort(url);
        HttpProtocolType protocolType = httpUrlProtocolType(url);

        // If host was specified then use it
        const String *host = NULL;

        if (cfgOptionIdxSource(cfgOptRepoStorageHost, repoIdx) != cfgSourceDefault)
        {
            const HttpUrl *const url = httpUrlNewParseP(
                cfgOptionIdxStr(cfgOptRepoStorageHost, repoIdx), .type = httpProtocolTypeAny, .defaultType = httpProtocolTypeHttps);

            host = httpUrlHost(url);
            port = httpUrlPort(url);
            protocolType = httpUrlProtocolType(url);
        }

        // If port was specified, overwrite the parsed/default port
        if (cfgOptionIdxSource(cfgOptRepoStoragePort, repoIdx) != cfgSourceDefault)
            port = cfgOptionIdxUInt(cfgOptRepoStoragePort, repoIdx);

        // Get role and token
        const StorageS3KeyType keyType = (StorageS3KeyType)cfgOptionIdxSeq(cfgOptRepoS3KeyType, repoIdx);
        const String *role = cfgOptionIdxStrNull(cfgOptRepoS3Role, repoIdx);
        const String *tokenFile = NULL;
        const String *credUrl = NULL;

        // If web identity authentication then load the role and token filename from environment variables documented here:
        // https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html
        if (keyType == storageS3KeyTypeWebId)
        {
            #define S3_ENV_AWS_ROLE_ARN                             "AWS_ROLE_ARN"
            #define S3_ENV_AWS_WEB_IDENTITY_TOKEN_FILE              "AWS_WEB_IDENTITY_TOKEN_FILE"

            const char *const roleZ = getenv(S3_ENV_AWS_ROLE_ARN);
            const char *const webIdTokenFileZ = getenv(S3_ENV_AWS_WEB_IDENTITY_TOKEN_FILE);

            if (roleZ == NULL || webIdTokenFileZ == NULL)
            {
                THROW_FMT(
                    OptionInvalidError,
                    "option '%s' is '" CFGOPTVAL_REPO_S3_KEY_TYPE_WEB_ID_Z "' but '" S3_ENV_AWS_ROLE_ARN "' and"
                    " '" S3_ENV_AWS_WEB_IDENTITY_TOKEN_FILE "' are not set",
                    cfgOptionIdxName(cfgOptRepoS3KeyType, repoIdx));
            }

            role = strNewZ(roleZ);
            tokenFile = strNewZ(webIdTokenFileZ);
        }
        // If pod identity authentication then load the credentials url and token filename from environment variables documented
        // here: https://docs.aws.amazon.com/eks/latest/userguide/pod-id-how-it-works.html
        else if (keyType == storageS3KeyTypePodId)
        {
            #define S3_ENV_AWS_CONTAINER_CREDENTIALS_FULL_URI       "AWS_CONTAINER_CREDENTIALS_FULL_URI"
            #define S3_ENV_AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE   "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"

            const char *const credUrlZ = getenv(S3_ENV_AWS_CONTAINER_CREDENTIALS_FULL_URI);
            const char *const podIdTokenFileZ = getenv(S3_ENV_AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE);

            if (credUrlZ == NULL || podIdTokenFileZ == NULL)
            {
                THROW_FMT(
                    OptionInvalidError,
                    "option '%s' is '" CFGOPTVAL_REPO_S3_KEY_TYPE_POD_ID_Z "' but '" S3_ENV_AWS_CONTAINER_CREDENTIALS_FULL_URI "'"
                    " and '" S3_ENV_AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE "' are not set",
                    cfgOptionIdxName(cfgOptRepoS3KeyType, repoIdx));
            }

            credUrl = strNewZ(credUrlZ);
            tokenFile = strNewZ(podIdTokenFileZ);
        }

        // If process credential authentication then get the command from config
        const StringList *const credCmd =
            keyType == storageS3KeyTypeProcess ? strLstNewVarLst(cfgOptionIdxLst(cfgOptRepoS3ProcessCmd, repoIdx)) : NULL;

        MEM_CONTEXT_PRIOR_BEGIN()
        {
            result = storageS3New(
                cfgOptionIdxStr(cfgOptRepoPath, repoIdx), write, storageRepoTargetTime(), pathExpressionCallback,
                cfgOptionIdxStr(cfgOptRepoS3Bucket, repoIdx), endPoint,
                cfgOptionIdxStr(cfgOptRepoS3Region, repoIdx), cfgOptionIdxStr(cfgOptRepoS3Service, repoIdx), keyType,
                (StorageS3UriStyle)cfgOptionIdxSeq(cfgOptRepoS3UriStyle, repoIdx), cfgOptionIdxStrNull(cfgOptRepoS3Key, repoIdx),
                cfgOptionIdxStrNull(cfgOptRepoS3KeySecret, repoIdx), cfgOptionIdxStrNull(cfgOptRepoS3Token, repoIdx),
                cfgOptionIdxStrNull(cfgOptRepoS3KmsKeyId, repoIdx), cfgOptionIdxStrNull(cfgOptRepoS3SseCustomerKey, repoIdx), role,
                tokenFile, credUrl, credCmd, cfgOptionIdxStrNull(cfgOptRepoS3StsHost, repoIdx),
                (size_t)cfgOptionIdxUInt64(cfgOptRepoStorageUploadChunkSize, repoIdx),
                cfgOptionIdxKvNull(cfgOptRepoStorageTag, repoIdx), host, port, ioTimeoutMs(), protocolType,
                cfgOptionIdxBool(cfgOptRepoStorageVerifyTls, repoIdx), cfgOptionIdxStrNull(cfgOptRepoStorageCaFile, repoIdx),
                cfgOptionIdxStrNull(cfgOptRepoStorageCaPath, repoIdx), cfgOptionIdxBool(cfgOptRepoS3RequesterPays, repoIdx));
        }
        MEM_CONTEXT_PRIOR_END();
    }
    MEM_CONTEXT_TEMP_END();

    FUNCTION_LOG_RETURN(STORAGE, result);
}
