package tidbcloudlogin

import (
	"errors"
	"sync"

	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"

	account "github.com/tidbcloud/tidb-management-service/functional-test/pkg/account/client"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/account/client/tenant_service"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/coreportalapi/client"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/coreportalapi/client/test_token_service"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/coreportalapi/model"
)

type WebApiLoginContext struct {
	Host              string
	Auth0Domain       string
	Auth0ClientID     string
	Auth0ClientSecret string
	UserEmail         string
	BearerToken       string

	mu sync.Mutex
}

func (ctx *WebApiLoginContext) LoginAndGetToken(orgId string) (string, error) {
	ctx.mu.Lock()
	defer ctx.mu.Unlock()

	if ctx.Host == "" {
		return "", errors.New("login context is not complete: empty host")
	}

	if ctx.BearerToken != "" {
		var err error
		ctx.UserEmail, err = ctx.getUserEmail(ctx.Host, orgId, ctx.BearerToken)
		if err != nil {
			return "", err
		}

		return ctx.BearerToken, nil
	}

	if ctx.Auth0Domain == "" {
		return "", errors.New("login context is not complete: empty auth0 domain")
	}
	if ctx.Auth0ClientID == "" {
		return "", errors.New("login context is not complete: empty auth0 client id")
	}
	if ctx.Auth0ClientSecret == "" {
		return "", errors.New("login context is not complete: empty auth0 client secret")
	}
	if ctx.UserEmail == "" {
		return "", errors.New("login context is not complete: empty user email")
	}

	var err error
	ctx.BearerToken, err = ctx.getToken(
		ctx.Host,
		ctx.Auth0Domain,
		ctx.Auth0ClientID,
		ctx.Auth0ClientSecret,
		ctx.UserEmail,
	)
	if err != nil {
		return "", err
	}

	return ctx.BearerToken, nil
}

// getToken requests a token from tidbcloud test api `POST /api/v1/test-token`.
// The returned token is used as bearer token to access tidbcloud api.
// Parameters:
//
//   - host: tidbcloud api host, e.g. "us-west-2.staging.shared.aws.tidbcloud.com"
//   - auth0Domain: auth0 domain, e.g. "tidb-staging.us.auth0.com"
//   - auth0ClientID: auth0 client id, e.g. "xxxxx"
//   - auth0ClientSecret: auth0 client secret, e.g. "xxxxx"
//   - email: which user is logging into tidbcloud, e.g. "xuyifan02@pingcap.com"
func (ctx *WebApiLoginContext) getToken(
	host, auth0Domain, auth0ClientID, auth0ClientSecret, email string,
) (string, error) {
	trans := httptransport.New(host, "", []string{"https"})
	cli := client.New(trans, strfmt.Default)

	params := test_token_service.NewTestTokenServiceGenerateTestTokenParams()
	params.SetBody(&model.CentralGenerateTestTokenReq{
		Auth0ClientID:     auth0ClientID,
		Auth0ClientSecret: auth0ClientSecret,
		Auth0Domain:       auth0Domain,
		Email:             email,
	})

	resp, err := cli.TestTokenService.TestTokenServiceGenerateTestToken(params, nil)
	if err != nil {
		return "", err
	}

	if resp.Payload == nil {
		return "", nil
	}

	return resp.Payload.Token, nil
}

func (ctx *WebApiLoginContext) getUserEmail(host, orgId, token string) (string, error) {
	trans := httptransport.New(host, "", []string{"https"})
	trans.DefaultAuthentication = httptransport.BearerToken(token)
	cli := account.New(trans, strfmt.Default)

	params := tenant_service.NewGetUserProfileParams()
	params.SetOrgID(orgId)

	resp, err := cli.TenantService.GetUserProfile(params, nil)
	if err != nil {
		return "", err
	}

	if resp.Payload == nil {
		return "", errors.New("failed to get user profile")
	}

	if resp.Payload.Email != nil {
		return *resp.Payload.Email, nil
	}

	return "", errors.New("failed to get user profile")
}
