package clientservices

import (
	"fmt"
	"net"
	"net/http"
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
)

type ServiceClientOptions struct {
	// The name of the domain in which the token will be issued.
	DomainName string

	// Credentials to auth with.
	Username string
	Password string

	// Optional field for setting project scope.
	ProjectID string

	// Optional field. The name of the domain where the user resides (Identity v3).
	UserDomainName string

	// Field for setting Identity endpoint.
	AuthURL string

	// Field for setting location for endpoints like ResellAPI or Keystone.
	AuthRegion string

	// Optional field.
	HTTPClient *http.Client

	// Optional field.
	UserAgent string
}

func NewServiceClient(options *ServiceClientOptions) (*gophercloud.ServiceClient, error) {
	// UserDomainName field to specify the domain name where the user is located.
	// If this field is not specified, then we will think that the token will be
	// issued in the same domain where the user is located.
	if options.UserDomainName == "" {
		options.UserDomainName = options.DomainName
	}

	authOptions := gophercloud.AuthOptions{
		AllowReauth:      true,
		IdentityEndpoint: options.AuthURL,
		Username:         options.Username,
		Password:         options.Password,
		DomainName:       options.UserDomainName,
		Scope: &gophercloud.AuthScope{
			ProjectID: options.ProjectID,
		},
	}

	// If project scope is not set, we use domain scope.
	if authOptions.Scope.ProjectID == "" {
		authOptions.Scope.DomainName = options.DomainName
	}

	authProvider, err := openstack.AuthenticatedClient(authOptions)
	if err != nil {
		return nil, fmt.Errorf("failed to create auth provider, err: %w", err)
	}

	serviceClient, err := openstack.NewIdentityV3(authProvider, gophercloud.EndpointOpts{
		Availability: gophercloud.AvailabilityPublic,
		Region:       options.AuthRegion,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create service client, err: %w", err)
	}

	httpClient := options.HTTPClient
	if httpClient == nil {
		httpClient = NewHTTPClient()
	}
	serviceClient.HTTPClient = *httpClient

	if options.UserAgent != "" {
		serviceClient.UserAgent.Prepend(options.UserAgent)
	}

	return serviceClient, nil
}

// ---------------------------------------------------------------------------------------------------------------------

const (
	// httpTimeout represents the default timeout (in seconds) for HTTP
	// requests.
	httpTimeout = 120

	// dialTimeout represents the default timeout (in seconds) for HTTP
	// connection establishments.
	dialTimeout = 60

	// keepaliveTimeout represents the default keep-alive period for an active
	// network connection.
	keepaliveTimeout = 60

	// maxIdleConns represents the maximum number of idle (keep-alive)
	// connections.
	maxIdleConns = 100

	// idleConnTimeout represents the maximum amount of time an idle
	// (keep-alive) connection will remain idle before closing itself.
	idleConnTimeout = 100

	// tlsHandshakeTimeout represents the default timeout (in seconds)
	// for TLS handshake.
	tlsHandshakeTimeout = 60

	// expectContinueTimeout represents the default amount of time to
	// wait for a server's first response headers.
	expectContinueTimeout = 1
)

func NewHTTPClient() *http.Client {
	return &http.Client{
		Timeout: time.Second * httpTimeout,
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   dialTimeout * time.Second,
				KeepAlive: keepaliveTimeout * time.Second,
			}).DialContext,
			MaxIdleConns:          maxIdleConns,
			IdleConnTimeout:       idleConnTimeout * time.Second,
			TLSHandshakeTimeout:   tlsHandshakeTimeout * time.Second,
			ExpectContinueTimeout: expectContinueTimeout * time.Second,
		},
	}
}
