package ping

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/rs/zerolog"

	"github.com/pomerium/pomerium/internal/encoding"
	"github.com/pomerium/pomerium/internal/httputil"
)

type config struct {
	authURL        *url.URL
	apiURL         *url.URL
	serviceAccount *ServiceAccount
	httpClient     *http.Client
	environmentID  string
}

// An Option updates the Ping configuration.
type Option func(*config)

// WithAPIURL sets the api url in the config.
func WithAPIURL(apiURL *url.URL) Option {
	return func(cfg *config) {
		cfg.apiURL = apiURL
	}
}

// WithAuthURL sets the auth url in the config.
func WithAuthURL(authURL *url.URL) Option {
	return func(cfg *config) {
		cfg.authURL = authURL
	}
}

// WithEnvironmentID sets the environment ID in the config.
func WithEnvironmentID(environmentID string) Option {
	return func(cfg *config) {
		cfg.environmentID = environmentID
	}
}

// WithHTTPClient sets the http client option.
func WithHTTPClient(httpClient *http.Client) Option {
	return func(cfg *config) {
		cfg.httpClient = httputil.NewLoggingClient(httpClient, "ping_idp_client",
			func(evt *zerolog.Event) *zerolog.Event {
				return evt.Str("provider", "ping")
			})
	}
}

// WithProviderURL sets the environment ID from the provider URL set in the config.
func WithProviderURL(providerURL *url.URL) Option {
	// provider URL will be https://auth.pingone.com/{ENVIRONMENT_ID}/as
	if providerURL == nil {
		return func(cfg *config) {}
	}
	parts := strings.Split(providerURL.Path, "/")
	if len(parts) < 1 {
		return func(cfg *config) {}
	}
	return WithEnvironmentID(parts[1])
}

// WithServiceAccount sets the service account in the config.
func WithServiceAccount(serviceAccount *ServiceAccount) Option {
	return func(cfg *config) {
		cfg.serviceAccount = serviceAccount
	}
}

func getConfig(options ...Option) *config {
	cfg := new(config)
	WithHTTPClient(http.DefaultClient)(cfg)
	WithAuthURL(&url.URL{
		Scheme: "https",
		Host:   "auth.pingone.com",
	})(cfg)
	WithAPIURL(&url.URL{
		Scheme: "https",
		Host:   "api.pingone.com",
	})(cfg)
	for _, option := range options {
		option(cfg)
	}
	return cfg
}

// A ServiceAccount is used by the Ping provider to query the API.
type ServiceAccount struct {
	ClientID      string `json:"client_id"`
	ClientSecret  string `json:"client_secret"`
	EnvironmentID string `json:"environment_id"`
}

// ParseServiceAccount parses the service account in the config options.
func ParseServiceAccount(rawServiceAccount string) (*ServiceAccount, error) {
	var serviceAccount ServiceAccount
	if err := encoding.DecodeBase64OrJSON(rawServiceAccount, &serviceAccount); err != nil {
		return nil, err
	}

	if serviceAccount.ClientID == "" {
		return nil, fmt.Errorf("client_id is required")
	}
	if serviceAccount.ClientSecret == "" {
		return nil, fmt.Errorf("client_secret is required")
	}

	return &serviceAccount, nil
}