package auth0

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

	"github.com/patrickmn/go-cache"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"go.uber.org/zap"
)

type Token struct {
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`
	Scope       string `json:"scope"`
	ExpireIn    int    `json:"expires_in"`
}

var lck sync.Mutex
var auth0TokenCache = cache.New(86400*time.Second, 1*time.Minute)

// GetClientToken  returns the auth0 client token
func GetClientToken(domain, clientID, clientSecret, audience string) (string, error) {
	v, found := auth0TokenCache.Get(clientID)
	if found {
		return v.(string), nil
	}
	lck.Lock()
	defer lck.Unlock()

	v, found = auth0TokenCache.Get(clientID)
	if found {
		return v.(string), nil
	}

	url := fmt.Sprintf("https://%s/oauth/token", domain)
	payload := strings.NewReader(fmt.Sprintf("{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"audience\":\"%s\",\"grant_type\":\"client_credentials\"}", clientID, clientSecret, audience))
	req, err := http.NewRequest("POST", url, payload)
	if err != nil {
		return "", err
	}
	req.Header.Add("content-type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer DeferClose(res.Body)
	if res.StatusCode != 200 {
		return "", fmt.Errorf("auth0 server return non 200 status code, %d", res.StatusCode)
	}

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return "", err
	}

	var token Token
	err = json.Unmarshal(body, &token)
	if err != nil {
		return "", err
	}
	err = auth0TokenCache.Add(clientID, token.AccessToken, time.Duration(token.ExpireIn)*time.Second-1*time.Minute)
	if err != nil {
		log.Error("add token to cache failed", zap.String("clientId", clientID), zap.String("audience", audience), zap.Error(err))
	}
	return token.AccessToken, nil
}

func DeferClose(c io.Closer) {
	if err := c.Close(); err != nil {
		log.Error("close failed", zap.Error(err))
	}
}
