package internal

import (
	"context"
	"crypto/tls"
	"fmt"
	"net/url"

	log "github.com/sirupsen/logrus"
	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	mgm "github.com/netbirdio/netbird/management/client"
)

// PKCEAuthorizationFlow represents PKCE Authorization Flow information
type PKCEAuthorizationFlow struct {
	ProviderConfig PKCEAuthProviderConfig
}

// PKCEAuthProviderConfig has all attributes needed to initiate pkce authorization flow
type PKCEAuthProviderConfig struct {
	// ClientID An IDP application client id
	ClientID string
	// ClientSecret An IDP application client secret
	ClientSecret string
	// Audience An Audience for to authorization validation
	Audience string
	// TokenEndpoint is the endpoint of an IDP manager where clients can obtain access token
	TokenEndpoint string
	// AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code
	AuthorizationEndpoint string
	// Scopes provides the scopes to be included in the token request
	Scope string
	// RedirectURL handles authorization code from IDP manager
	RedirectURLs []string
	// UseIDToken indicates if the id token should be used for authentication
	UseIDToken bool
	// ClientCertPair is used for mTLS authentication to the IDP
	ClientCertPair *tls.Certificate
	// DisablePromptLogin makes the PKCE flow to not prompt the user for login
	DisablePromptLogin bool
}

// GetPKCEAuthorizationFlowInfo initialize a PKCEAuthorizationFlow instance and return with it
func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL *url.URL, clientCert *tls.Certificate) (PKCEAuthorizationFlow, error) {
	// validate our peer's Wireguard PRIVATE key
	myPrivateKey, err := wgtypes.ParseKey(privateKey)
	if err != nil {
		log.Errorf("failed parsing Wireguard key %s: [%s]", privateKey, err.Error())
		return PKCEAuthorizationFlow{}, err
	}

	var mgmTLSEnabled bool
	if mgmURL.Scheme == "https" {
		mgmTLSEnabled = true
	}

	log.Debugf("connecting to Management Service %s", mgmURL.String())
	mgmClient, err := mgm.NewClient(ctx, mgmURL.Host, myPrivateKey, mgmTLSEnabled)
	if err != nil {
		log.Errorf("failed connecting to Management Service %s %v", mgmURL.String(), err)
		return PKCEAuthorizationFlow{}, err
	}
	log.Debugf("connected to the Management service %s", mgmURL.String())

	defer func() {
		err = mgmClient.Close()
		if err != nil {
			log.Warnf("failed to close the Management service client %v", err)
		}
	}()

	serverKey, err := mgmClient.GetServerPublicKey()
	if err != nil {
		log.Errorf("failed while getting Management Service public key: %v", err)
		return PKCEAuthorizationFlow{}, err
	}

	protoPKCEAuthorizationFlow, err := mgmClient.GetPKCEAuthorizationFlow(*serverKey)
	if err != nil {
		if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
			log.Warnf("server couldn't find pkce flow, contact admin: %v", err)
			return PKCEAuthorizationFlow{}, err
		}
		log.Errorf("failed to retrieve pkce flow: %v", err)
		return PKCEAuthorizationFlow{}, err
	}

	authFlow := PKCEAuthorizationFlow{
		ProviderConfig: PKCEAuthProviderConfig{
			Audience:              protoPKCEAuthorizationFlow.GetProviderConfig().GetAudience(),
			ClientID:              protoPKCEAuthorizationFlow.GetProviderConfig().GetClientID(),
			ClientSecret:          protoPKCEAuthorizationFlow.GetProviderConfig().GetClientSecret(),
			TokenEndpoint:         protoPKCEAuthorizationFlow.GetProviderConfig().GetTokenEndpoint(),
			AuthorizationEndpoint: protoPKCEAuthorizationFlow.GetProviderConfig().GetAuthorizationEndpoint(),
			Scope:                 protoPKCEAuthorizationFlow.GetProviderConfig().GetScope(),
			RedirectURLs:          protoPKCEAuthorizationFlow.GetProviderConfig().GetRedirectURLs(),
			UseIDToken:            protoPKCEAuthorizationFlow.GetProviderConfig().GetUseIDToken(),
			ClientCertPair:        clientCert,
			DisablePromptLogin:    protoPKCEAuthorizationFlow.GetProviderConfig().GetDisablePromptLogin(),
		},
	}

	err = isPKCEProviderConfigValid(authFlow.ProviderConfig)
	if err != nil {
		return PKCEAuthorizationFlow{}, err
	}

	return authFlow, nil
}

func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error {
	errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
	if config.ClientID == "" {
		return fmt.Errorf(errorMSGFormat, "Client ID")
	}
	if config.TokenEndpoint == "" {
		return fmt.Errorf(errorMSGFormat, "Token Endpoint")
	}
	if config.AuthorizationEndpoint == "" {
		return fmt.Errorf(errorMSGFormat, "Authorization Auth Endpoint")
	}
	if config.Scope == "" {
		return fmt.Errorf(errorMSGFormat, "PKCE Auth Scopes")
	}
	if config.RedirectURLs == nil {
		return fmt.Errorf(errorMSGFormat, "PKCE Redirect URLs")
	}
	return nil
}
