/*
 * CLOUD API
 *
 *  IONOS Enterprise-grade Infrastructure as a Service (IaaS) solutions can be managed through the Cloud API, in addition or as an alternative to the \"Data Center Designer\" (DCD) browser-based tool.    Both methods employ consistent concepts and features, deliver similar power and flexibility, and can be used to perform a multitude of management tasks, including adding servers, volumes, configuring networks, and so on.
 *
 * API version: 6.0
 */

// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

package ionoscloud

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	IonosUsernameEnvVar   = "IONOS_USERNAME"
	IonosPasswordEnvVar   = "IONOS_PASSWORD"
	IonosTokenEnvVar      = "IONOS_TOKEN"
	IonosApiUrlEnvVar     = "IONOS_API_URL"
	IonosPinnedCertEnvVar = "IONOS_PINNED_CERT"
	IonosLogLevelEnvVar   = "IONOS_LOG_LEVEL"
	IonosContractNumber   = "IONOS_CONTRACT_NUMBER"
	DefaultIonosServerUrl = "https://api.ionos.com/cloudapi/v6"
	DefaultIonosBasePath  = "/cloudapi/v6"
	defaultMaxRetries     = 3
	defaultWaitTime       = 100 * time.Millisecond
	defaultMaxWaitTime    = 2000 * time.Millisecond
	defaultPollInterval   = 1 * time.Second
)

// contextKeys are used to identify the type of value in the context.
// Since these are string, it is possible to get a short description of the
// context key for logging and debugging using key.String().

type contextKey string

func (c contextKey) String() string {
	return "auth " + string(c)
}

var (
	// ContextOAuth2 takes an oauth2.TokenSource as authentication for the request.
	ContextOAuth2 = contextKey("token")

	// ContextBasicAuth takes BasicAuth as authentication for the request.
	ContextBasicAuth = contextKey("basic")

	// ContextAccessToken takes a string oauth2 access token as authentication for the request.
	ContextAccessToken = contextKey("accesstoken")

	// ContextAPIKeys takes a string apikey as authentication for the request
	ContextAPIKeys = contextKey("apiKeys")

	// ContextHttpSignatureAuth takes HttpSignatureAuth as authentication for the request.
	ContextHttpSignatureAuth = contextKey("httpsignature")

	// ContextServerIndex uses a server configuration from the index.
	ContextServerIndex = contextKey("serverIndex")

	// ContextOperationServerIndices uses a server configuration from the index mapping.
	ContextOperationServerIndices = contextKey("serverOperationIndices")

	// ContextServerVariables overrides a server configuration variables.
	ContextServerVariables = contextKey("serverVariables")

	// ContextOperationServerVariables overrides a server configuration variables using operation specific values.
	ContextOperationServerVariables = contextKey("serverOperationVariables")
)

// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth
type BasicAuth struct {
	UserName string `json:"userName,omitempty"`
	Password string `json:"password,omitempty"`
}

// APIKey provides API key based authentication to a request passed via context using ContextAPIKey
type APIKey struct {
	Key    string
	Prefix string
}

// ServerVariable stores the information about a server variable
type ServerVariable struct {
	Description  string
	DefaultValue string
	EnumValues   []string
}

// ServerConfiguration stores the information about a server
type ServerConfiguration struct {
	URL         string
	Description string
	Variables   map[string]ServerVariable
}

// ServerConfigurations stores multiple ServerConfiguration items
type ServerConfigurations []ServerConfiguration

// Configuration stores the configuration of the API client
type Configuration struct {
	Host               string            `json:"host,omitempty"`
	Scheme             string            `json:"scheme,omitempty"`
	DefaultHeader      map[string]string `json:"defaultHeader,omitempty"`
	DefaultQueryParams url.Values        `json:"defaultQueryParams,omitempty"`
	UserAgent          string            `json:"userAgent,omitempty"`
	// Debug is deprecated, will be replaced by LogLevel
	Debug            bool `json:"debug,omitempty"`
	Servers          ServerConfigurations
	OperationServers map[string]ServerConfigurations
	HTTPClient       *http.Client
	Username         string        `json:"username,omitempty"`
	Password         string        `json:"password,omitempty"`
	Token            string        `json:"token,omitempty"`
	MaxRetries       int           `json:"maxRetries,omitempty"`
	WaitTime         time.Duration `json:"waitTime,omitempty"`
	MaxWaitTime      time.Duration `json:"maxWaitTime,omitempty"`
	PollInterval     time.Duration `json:"pollInterval,omitempty"`
	LogLevel         LogLevel
	Logger           Logger
}

// NewConfiguration returns a new Configuration object
func NewConfiguration(username, password, token, hostUrl string) *Configuration {
	cfg := &Configuration{
		DefaultHeader:      make(map[string]string),
		DefaultQueryParams: url.Values{},
		UserAgent:          "ionos-cloud-sdk-go/v6.3.4",
		Debug:              false,
		Username:           username,
		Password:           password,
		Token:              token,
		MaxRetries:         defaultMaxRetries,
		MaxWaitTime:        defaultMaxWaitTime,
		WaitTime:           defaultWaitTime,
		PollInterval:       defaultPollInterval,
		Logger:             NewDefaultLogger(),
		LogLevel:           getLogLevelFromEnv(),
		Host:               getHost(hostUrl),
		Scheme:             getScheme(hostUrl),
		Servers: ServerConfigurations{
			{
				URL:         "https://api.ionos.com/cloudapi/v6",
				Description: "No description provided",
			},
		},
		OperationServers: map[string]ServerConfigurations{},
	}
	if hostUrl != "" {
		cfg.Servers = ServerConfigurations{
			{
				URL:         getServerUrl(hostUrl),
				Description: "overriden endpoint",
			},
		}
	}
	return cfg
}

func NewConfigurationFromEnv() *Configuration {
	return NewConfiguration(os.Getenv(IonosUsernameEnvVar), os.Getenv(IonosPasswordEnvVar), os.Getenv(IonosTokenEnvVar), os.Getenv(IonosApiUrlEnvVar))
}

// SetDepth sets the depth query param for all the requests
func (c *Configuration) SetDepth(depth int32) {
	c.DefaultQueryParams["depth"] = []string{strconv.Itoa(int(depth))}
}

// AddDefaultHeader adds a new HTTP header to the default header in the request
func (c *Configuration) AddDefaultHeader(key string, value string) {
	c.DefaultHeader[key] = value
}

func (c *Configuration) AddDefaultQueryParam(key string, value string) {
	c.DefaultQueryParams[key] = []string{value}
}

// URL formats template on a index using given variables
func (sc ServerConfigurations) URL(index int, variables map[string]string) (string, error) {
	if index < 0 || len(sc) <= index {
		return "", fmt.Errorf("Index %v out of range %v", index, len(sc)-1)
	}
	server := sc[index]
	url := server.URL
	if !strings.Contains(url, "http://") && !strings.Contains(url, "https://") {
		return "", fmt.Errorf(
			"the URL provided appears to be missing the protocol scheme prefix (\"https://\" or \"http://\"), please verify and try again: %s",
			url,
		)
	}

	// go through variables and replace placeholders
	for name, variable := range server.Variables {
		if value, ok := variables[name]; ok {
			found := bool(len(variable.EnumValues) == 0)
			for _, enumValue := range variable.EnumValues {
				if value == enumValue {
					found = true
				}
			}
			if !found {
				return "", fmt.Errorf("The variable %s in the server URL has invalid value %v. Must be %v", name, value, variable.EnumValues)
			}
			url = strings.Replace(url, "{"+name+"}", value, -1)
		} else {
			url = strings.Replace(url, "{"+name+"}", variable.DefaultValue, -1)
		}
	}
	return url, nil
}

// ServerURL returns URL based on server settings
func (c *Configuration) ServerURL(index int, variables map[string]string) (string, error) {
	return c.Servers.URL(index, variables)
}

func getServerIndex(ctx context.Context) (int, error) {
	si := ctx.Value(ContextServerIndex)
	if si != nil {
		if index, ok := si.(int); ok {
			return index, nil
		}
		return 0, reportError("Invalid type %T should be int", si)
	}
	return 0, nil
}

func getServerOperationIndex(ctx context.Context, endpoint string) (int, error) {
	osi := ctx.Value(ContextOperationServerIndices)
	if osi != nil {
		if operationIndices, ok := osi.(map[string]int); !ok {
			return 0, reportError("Invalid type %T should be map[string]int", osi)
		} else {
			index, ok := operationIndices[endpoint]
			if ok {
				return index, nil
			}
		}
	}
	return getServerIndex(ctx)
}

func getServerVariables(ctx context.Context) (map[string]string, error) {
	sv := ctx.Value(ContextServerVariables)
	if sv != nil {
		if variables, ok := sv.(map[string]string); ok {
			return variables, nil
		}
		return nil, reportError("ctx value of ContextServerVariables has invalid type %T should be map[string]string", sv)
	}
	return nil, nil
}

func getServerOperationVariables(ctx context.Context, endpoint string) (map[string]string, error) {
	osv := ctx.Value(ContextOperationServerVariables)
	if osv != nil {
		if operationVariables, ok := osv.(map[string]map[string]string); !ok {
			return nil, reportError("ctx value of ContextOperationServerVariables has invalid type %T should be map[string]map[string]string", osv)
		} else {
			variables, ok := operationVariables[endpoint]
			if ok {
				return variables, nil
			}
		}
	}
	return getServerVariables(ctx)
}

func getServerUrl(serverUrl string) string {
	if serverUrl == "" {
		return DefaultIonosServerUrl
	}

	if !strings.HasSuffix(serverUrl, DefaultIonosBasePath) {
		serverUrl = fmt.Sprintf("%s%s", serverUrl, DefaultIonosBasePath)
	}

	return EnsureURLFormat(serverUrl)
}

func getHost(serverUrl string) string {
	// url.Parse only interprets the host correctly when the scheme is set, so we prepend one here if needed
	if !strings.HasPrefix(serverUrl, "https://") && !strings.HasPrefix(serverUrl, "http://") {
		serverUrl = "http://" + serverUrl
	}
	url, err := url.Parse(serverUrl)
	if err != nil {
		return ""
	}
	return url.Host
}

func getScheme(serverUrl string) string {
	url, err := url.Parse(serverUrl)
	if err != nil {
		return ""
	}
	return url.Scheme
}

// ServerURLWithContext returns a new server URL given an endpoint
func (c *Configuration) ServerURLWithContext(ctx context.Context, endpoint string) (string, error) {
	sc, ok := c.OperationServers[endpoint]
	if !ok {
		sc = c.Servers
	}

	if ctx == nil {
		return sc.URL(0, nil)
	}

	index, err := getServerOperationIndex(ctx, endpoint)
	if err != nil {
		return "", err
	}

	variables, err := getServerOperationVariables(ctx, endpoint)
	if err != nil {
		return "", err
	}

	return sc.URL(index, variables)
}
