#include <config.h>

#define _GNU_SOURCE
#include <stdlib.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include "incluster_config.h"
#include "kube_config_common.h"

#define SERVICE_HOST_ENV_NAME "KUBERNETES_SERVICE_HOST"
#define SERVICE_PORT_ENV_NAME "KUBERNETES_SERVICE_PORT"
#define SERVICE_HTTPS_PREFIX "https://"
#define SERVICE_TOKEN_FILENAME "/var/run/secrets/kubernetes.io/serviceaccount/token"
#define SERVICE_CERT_FILENAME "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"

static int checkServiceAccountFile(const char *fileName)
{
    static char fname[] = "checkServiceAccountFile()";

    if (!fileName) {
        fprintf(stderr, "%s: The file name must be specified.\n", fname);
        return -1;
    }

    struct stat info;
    if (-1 == stat(fileName, &info)) {
        switch (errno) {
        case ENOENT:
            fprintf(stderr, "%s: The file %s does not exist.[%s]\n", fname, fileName, strerror(errno));
            return -1;
        case EACCES:
            fprintf(stderr, "%s: No permission to read the file %s.[%s]\n", fname, fileName, strerror(errno));
            return -1;
        default:
            fprintf(stderr, "%s: Cannot retrieve the information of file %s.[%s]\n", fname, fileName, strerror(errno));
            return -1;
        }
    }

    return 0;
}

static int setBasePathInCluster(char **pBasePath)
{
    static char fname[] = "setBasePathInCluster()";

#if defined(HAVE_SECURE_GETENV)
    const char *service_host_env = secure_getenv(SERVICE_HOST_ENV_NAME);
#elif defined(HAVE_GETENV)
    const char *service_host_env = getenv(SERVICE_HOST_ENV_NAME);
#else
    const char *service_host_env = NULL;
#endif
    if (!service_host_env) {
        fprintf(stderr, "%s: Cannot retrieve the kubernetes service host inside a pod by the env %s.\n", fname, SERVICE_HOST_ENV_NAME);
        return -1;
    }
#if defined(HAVE_SECURE_GETENV)
    const char *service_port_env = secure_getenv(SERVICE_PORT_ENV_NAME);
#elif defined(HAVE_GETENV)
    const char *service_port_env = getenv(SERVICE_PORT_ENV_NAME);
#else
    const char *service_port_env = NULL;
#endif
    if (!service_port_env) {
        fprintf(stderr, "%s: Cannot retrieve the kubernetes service port inside a pod by the env %s.\n", fname, SERVICE_PORT_ENV_NAME);
        return -1;
    }

    int basePathSize = strlen(SERVICE_HTTPS_PREFIX) + strlen(service_host_env) + strlen(service_port_env) + 2 /* 1 for ':', 1 for '\0' */ ;
    bool isIPv6 = false;
    if (strchr(service_host_env, ':') != NULL) {
        isIPv6 = true;
        // Takes into account the square brackets to escape the IP v6 address.
        basePathSize += 2;
    }
    char *basePath = calloc(basePathSize, sizeof(char));
    if (!basePath) {
        fprintf(stderr, "%s: Cannot allocate the memory for base path for kubernetes service.\n", fname);
        return -1;
    }

    if (isIPv6) {
        snprintf(basePath, basePathSize, "%s[%s]:%s", SERVICE_HTTPS_PREFIX, service_host_env, service_port_env);
    } else {
        snprintf(basePath, basePathSize, "%s%s:%s", SERVICE_HTTPS_PREFIX, service_host_env, service_port_env);
    }
    *pBasePath = basePath;
    return 0;
}

static int setSslConfigInCluster(sslConfig_t ** pSslConfig, const char *caFileName)
{
    static char fname[] = "setSslConfigInCluster()";

    if (0 != checkServiceAccountFile(caFileName)) {
        fprintf(stderr, "%s: Cannot retrieve the CA for kubernetes service in the pod.\n", fname);
        return -1;
    }

    sslConfig_t *sc = sslConfig_create(NULL,    /* client certificate */
                                       NULL,    /* client private key */
                                       caFileName,  /* CA file */
                                       0    /* 0 means the server certificate is required to be verified by CA */
        );
    if (!sc) {
        return -1;
    }

    *pSslConfig = sc;
    return 0;
}

static int readTokenInCluster(char *token, int token_buf_size)
{
    static char fname[] = "readTokenInCluster()";

    if (0 != checkServiceAccountFile(SERVICE_TOKEN_FILENAME)) {
        fprintf(stderr, "%s: Cannot retrieve the token for kubernetes service in the pod.\n", fname);
        return -1;
    }

    FILE *fp;
    fp = fopen(SERVICE_TOKEN_FILENAME, "r");
    if (NULL == fp) {
        fprintf(stderr, "%s: Failed to open file %s.[%s]", fname, SERVICE_TOKEN_FILENAME, strerror(errno));
        return -1;
    }

    char *read_str = fgets(token, token_buf_size, fp);
    fclose(fp);
    if (NULL == read_str) {
        fprintf(stderr, "%s: Cannot read token from the file %s.[%s]", fname, SERVICE_TOKEN_FILENAME, strerror(errno));
        return -1;
    }

    return 0;
}

static int setApiKeysInCluster(list_t ** pApiKeys)
{
    static char fname[] = "setApiKeysInCluster()";

    list_t *apiKeys = list_createList();
    if (!apiKeys) {
        fprintf(stderr, "%s: Cannot allocate the memory for api kyes for kubernetes service.\n", fname);
        return -1;
    }

    char token[BEARER_TOKEN_BUFFER_SIZE];
    memset(token, 0, sizeof(token));
    if (0 == readTokenInCluster(token, BEARER_TOKEN_BUFFER_SIZE)) {
        char tokenValue[BEARER_TOKEN_BUFFER_SIZE];
        memset(tokenValue, 0, sizeof(tokenValue));
        snprintf(tokenValue, BEARER_TOKEN_BUFFER_SIZE, BEARER_TOKEN_TEMPLATE, token);
        keyValuePair_t *keyPairToken = keyValuePair_create(strdup(AUTH_TOKEN_KEY), strdup(tokenValue));
        list_addElement(apiKeys, keyPairToken);
    } else {
        fprintf(stderr, "%s: Cannot retrieve the service token in the pod.", fname);
        return -1;
    }

    *pApiKeys = apiKeys;
    return 0;
}

int load_incluster_config(char **pBasePath, sslConfig_t ** pSslConfig, list_t ** pApiKeys)
{
    static char fname[] = "load_incluster_config()";

    if (0 != setBasePathInCluster(pBasePath)) {
        fprintf(stderr, "%s: Cannot set the base path for API server inside cluster.\n", fname);
        return -1;
    }

    if (0 != setSslConfigInCluster(pSslConfig, SERVICE_CERT_FILENAME)) {
        fprintf(stderr, "%s: Cannot set the SSL Configuration for the client inside cluster.\n", fname);
        return -1;
    }

    if (0 != setApiKeysInCluster(pApiKeys)) {
        fprintf(stderr, "%s: Cannot set the service tokens for the client inside cluster.\n", fname);
        return -1;
    }

    return 0;
}
