package auth

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/tidbcloud/billing-verify-bills/pkg/provider/gcp/googleapis"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/option"
)

var (
	tokenFile   string
	audience    string
	targetSa    string
	credentials *google.Credentials
)

const GCPScope = "https://www.googleapis.com/auth/cloud-platform"

// fail fast when the environment variables are misconfigured
func InitWebIdentityEnv() error {
	tokenFile = os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
	if tokenFile == "" {
		return fmt.Errorf("AWS_WEB_IDENTITY_TOKEN_FILE environment variable is empty")
	}
	audience = os.Getenv("GCP_WORKLOAD_IDENTITY_AUDIENCE")
	if audience == "" {
		return fmt.Errorf("GCP_WORKLOAD_IDENTITY_AUDIENCE environment varialbe is empty")
	}
	targetSa = os.Getenv("GCP_IMPERSONATE_SA")
	if targetSa == "" {
		return fmt.Errorf("GCP_IMPERSONATE_SA environment variable is empty")
	}
	return nil
}

func InitWithLocalFile(filename string) error {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	localCredentials, err := google.CredentialsFromJSON(context.Background(), b, GCPScope)
	if err != nil {
		return err
	}
	credentials = localCredentials
	return nil
}

type oidcTokenSource struct {
	// workload identity audience, it can be retrieved by pulumi output, it has following format
	// "//iam.googleapis.com/projects/{projectId}/locations/global/workloadIdentityPools/${poolId}/providers/{providerId}"
	audience         string
	eksOidcTokenFile string // default "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
}

func (ts oidcTokenSource) Token() (*oauth2.Token, error) {
	tokenData, err := ioutil.ReadFile(ts.eksOidcTokenFile)
	if err != nil {
		return nil, err
	}
	ctx := context.Background()
	stsSvc, err := googleapis.NewStsService(ctx)
	if err != nil {
		return nil, err
	}
	req := googleapis.GoogleIdentityStsV1betaExchangeTokenRequest{
		Audience:           ts.audience,
		Scope:              GCPScope,
		GrantType:          "urn:ietf:params:oauth:grant-type:token-exchange",
		RequestedTokenType: "urn:ietf:params:oauth:token-type:access_token",
		SubjectToken:       string(tokenData),
		SubjectTokenType:   "urn:ietf:params:oauth:token-type:jwt",
	}
	resp, err := stsSvc.Token(&req).Context(ctx).Do()
	if err != nil {
		return nil, err
	}
	dur := time.Duration(resp.ExpiresIn) * time.Second
	expiry := time.Now().Add(dur)

	return &oauth2.Token{
		AccessToken: resp.AccessToken,
		TokenType:   resp.TokenType,
		Expiry:      expiry,
	}, nil
}

func DefaultTokenSource() (oauth2.TokenSource, error) {
	if credentials != nil {
		return credentials.TokenSource, nil
	}
	ts := oidcTokenSource{
		audience:         audience,
		eksOidcTokenFile: tokenFile,
	}
	return Impersonate(targetSa, ts)
}

func Impersonate(targetSa string, ts oauth2.TokenSource) (oauth2.TokenSource, error) {
	config := &googleapis.Config{
		Target: targetSa,
		Scopes: []string{GCPScope},
	}
	ctx := context.Background()
	return googleapis.TokenSource(ctx, ts, config)
}

// DefaultClientOptions returns client auth options for the impersonated service account
func DefaultClientOptions() ([]option.ClientOption, error) {
	ts, err := DefaultTokenSource()
	if err != nil {
		return nil, err
	}

	return []option.ClientOption{
		option.WithTokenSource(ts),
	}, nil
}
