package app

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"code.tnxs.net/taxnexus/lib/api/auth0/auth0_client/auth"
	"code.tnxs.net/taxnexus/lib/api/auth0/auth0_client/user"
	"code.tnxs.net/taxnexus/lib/api/auth0/auth0_models"
)

var auth0AuthToken string
var auth0Expires time.Time

// GetAuth0AuthToken is a func
func GetAuth0AuthToken() string {
	sugar.Infof("board.getAuth0AuthToken: 📥")
	if auth0Expires.After(time.Now()) {
		return auth0AuthToken
	}
	apiParams := auth.NewPostCredentialsParamsWithTimeout(getTimeout)
	apiParams.CredentialsRequest = &auth0_models.CredentialsRequest{
		ClientID:     GetServiceAccount("auth0").ClientID,
		ClientSecret: GetServiceAccount("auth0").ClientSecret,
		Audience:     "https://taxnexus.auth0.com/api/v2/",
		GrantType:    "client_credentials",
	}
	response, err := auth0Client.Auth.PostCredentials(apiParams)
	if err != nil {
		sugar.Errorf("board.getAuth0AuthToken: 💣 ⛔ : %w", err)
	}
	auth0Expires = time.Now().Add(time.Duration(response.Payload.ExpiresIn) * time.Second)
	auth0AuthToken = response.Payload.AccessToken
	sugar.Debugf("board.getAuth0AuthToken: 📏 new token: %s", auth0AuthToken)
	sugar.Infof("board.getAuth0AuthToken: 👍 📤 new token retrieved")
	return auth0AuthToken
}

// GetAuth0UserByEmail looks up Auth0 user by email
func GetAuth0UserByEmail(email string) (string, error) {
	sugar.Info("app.GetAuth0UserByEmail: 📥")
	authToken := GetAuth0AccessToken()
	if authToken == "" {
		return "", fmt.Errorf("app.GetAuth0UserByEmail: 💣⛔ Error obtaining bearer token")
	}
	auth0Params := user.NewGetUserByEmailParamsWithTimeout(getTimeout)
	auth0Params.Authorization = "Bearer " + authToken
	auth0Params.Email = email
	auth0Response, err := auth0Client.User.GetUserByEmail(auth0Params)
	if err != nil {
		return "", err
	}
	if len(auth0Response.Payload) == 0 {
		sugar.Infof("app.GetAuth0UserByEmail: 👍📤 %s not found", email)
		return constNotFound, nil
	}
	sugar.Infof("app.GetAuth0UserByEmail: 👍📤 %s found %s", email, auth0Response.Payload[0].UserID)
	return auth0Response.Payload[0].UserID, nil
}

// CreateNewAuth0User creates a new Auth0 User
func CreateNewAuth0User(newUser *NewAuth0User) (string, error) {
	sugar.Info("app.CreateNewAuth0User: 📥")
	authToken := GetAuth0AccessToken()
	if authToken == "" {
		return "", fmt.Errorf("app.CreateNewAuth0User: 💣⛔ Error obtaining bearer token")
	}
	auth0Params := user.NewPostUsersParamsWithTimeout(postTimeout)
	auth0Params.Authorization = "Bearer " + authToken
	auth0Params.UserRequest.Users = []*auth0_models.NewUser{
		{
			Email:         newUser.Email,
			Name:          newUser.Name,
			FamilyName:    newUser.FamilyName,
			GivenName:     newUser.GivenName,
			Nickname:      newUser.Nickname,
			Password:      newUser.Password,
			PhoneNumber:   newUser.PhoneNumber,
			Blocked:       newUser.Blocked,
			Connection:    newUser.Connection,
			EmailVerified: newUser.EmailVerified,
			PhoneVerified: newUser.PhoneVerified,
			Picture:       newUser.Picture,
			UserID:        newUser.UserID,
			Username:      newUser.Username,
			VerifyEmail:   newUser.VerifyEmail,
		},
	}
	_, err := auth0Client.User.PostUsers(auth0Params)
	if err != nil {
		return "", err
	}
	auth0UserID, err := GetAuth0UserByEmail(newUser.Email)
	if err != nil {
		return "", err
	}
	if auth0UserID != constNotFound {
		return "", fmt.Errorf("app.CreateNewAuth0User: 💣⛔ Auth0 user exists: %s", auth0UserID)
	}
	return auth0UserID, nil
}

type auth0TokenResponse struct {
	AccessToken string
	TokenType   string
}

const tokenURL = "https://taxnexus.auth0.com/oauth/token" //nolint:gosec // false positive
const tokenTimeout = 86400 * time.Second
const requestTemplate = `
{
	"client_id": "%s",
	"client_secret": "%s"
	"audience": "%s",
	"grant_type": "client_credentials"
}
`

var accessToken string
var authTokenTime time.Time

// GetAuth0AccessToken uses conf file values to get an Auth0 access token
func GetAuth0AccessToken() string {
	if time.Now().Before(authTokenTime) && accessToken != "" {
		return accessToken
	}
	serviceAccount := GetServiceAccount("auth0")
	payload := strings.NewReader(
		fmt.Sprintf(requestTemplate,
			serviceAccount.ClientID,
			serviceAccount.ClientSecret,
			serviceAccount.Endpoint.Scheme+"://"+
				serviceAccount.Endpoint.Host+
				serviceAccount.Endpoint.BasePath) + "/")
	req, err := http.NewRequest("POST", tokenURL, payload)
	if err != nil {
		return ""
	}
	req.Header.Add("content-type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return ""
	}
	defer res.Body.Close() //nolint:errcheck // it's defered
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return ""
	}
	var response *auth0TokenResponse
	err = json.Unmarshal(body, response)
	if err != nil {
		return ""
	}
	accessToken = response.AccessToken
	authTokenTime = time.Now().Add(tokenTimeout)
	return accessToken
}
