package googleapi

import (
	"encoding/json"
	"fmt"
	"gogs.mikescher.com/BlackForestBytes/goext/exerr"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"gogs.mikescher.com/BlackForestBytes/goext/timeext"
	"io"
	"net/http"
	"sync"
	"time"
)

type GoogleOAuth interface {
	AccessToken() (string, error)
}

type oauth struct {
	clientID     string
	clientSecret string
	refreshToken string

	lock        sync.RWMutex
	accessToken *string
	expiryDate  *time.Time
}

func NewGoogleOAuth(clientid string, clientsecret, refreshtoken string) GoogleOAuth {
	return &oauth{
		clientID:     clientid,
		clientSecret: clientsecret,
		refreshToken: refreshtoken,
	}
}

func (c *oauth) AccessToken() (string, error) {
	c.lock.RLock()
	if c.accessToken != nil && c.expiryDate != nil && (*c.expiryDate).After(time.Now()) {
		c.lock.RUnlock()
		return *c.accessToken, nil // still valid
	}
	c.lock.RUnlock()

	httpclient := http.Client{}

	url := fmt.Sprintf("https://oauth2.googleapis.com/token?client_id=%s&client_secret=%s&grant_type=%s&refresh_token=%s",
		c.clientID,
		c.clientSecret,
		"refresh_token",
		c.refreshToken)

	req, err := http.NewRequest(http.MethodPost, url, nil)
	if err != nil {
		return "", err
	}

	reqStartTime := time.Now()

	res, err := httpclient.Do(req)

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

	var r response

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

	err = json.Unmarshal(data, &r)
	if err != nil {
		return "", err
	}

	if r.ExpiresIn == 0 || r.AccessToken == "" {
		return "", exerr.New(exerr.TypeGoogleResponse, "google oauth returned no response").Str("body", string(data)).Build()
	}

	c.lock.Lock()
	c.expiryDate = langext.Ptr(reqStartTime.Add(timeext.FromSeconds(r.ExpiresIn - 10)))
	c.accessToken = langext.Ptr(r.AccessToken)
	c.lock.Unlock()

	return r.AccessToken, nil
}