package feishu

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

	"github.com/pingcap/errors"
)

type HttpClient interface {
	Do(r *http.Request) (*http.Response, error)
}

type HttpAuthenticator interface {
	Auth(r *http.Request) error
}

type TenantTokenAuthenticator struct {
	appId       string
	appSecret   string
	cachedToken string
	expireTime  time.Time
	lck         sync.RWMutex
}

func (auth *TenantTokenAuthenticator) Auth(req *http.Request) error {
	var token string
	auth.lck.RLock()
	if auth.cachedToken == "" || time.Now().After(auth.expireTime) {
		auth.lck.RUnlock()
		if err := auth.tryRefreshToken(); err != nil {
			return errors.Trace(err)
		}
		token = auth.cachedToken
	} else {
		auth.lck.RUnlock()
		token = auth.cachedToken
	}
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
	return nil
}

func (auth *TenantTokenAuthenticator) tryRefreshToken() error {
	auth.lck.Lock()
	defer auth.lck.Unlock()
	if auth.cachedToken == "" || time.Now().After(auth.expireTime) {
		url := "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
		data, err := json.Marshal(&TenantAccessTokenReq{
			AppId:     auth.appId,
			AppSecret: auth.appSecret,
		})
		if err != nil {
			return errors.Trace(err)
		}
		payload := strings.NewReader(string(data))
		req, err := http.NewRequest("POST", url, payload)
		if err != nil {
			return errors.Trace(err)
		}
		req.Header.Add("content-type", "application/json")
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			return errors.Trace(err)
		}
		defer deferClose(res.Body)
		if res.StatusCode != 200 {
			return fmt.Errorf("server return non 200 status code, %d", res.StatusCode)
		}
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return errors.Trace(err)
		}

		var token TenantAccessTokenResp
		err = json.Unmarshal(body, &token)
		if err != nil {
			return errors.Trace(err)
		}
		if token.Code != 0 {
			return fmt.Errorf("api return non 0  code, %d", token.Code)
		}
		auth.cachedToken = token.TenantAccessToken
		auth.expireTime = auth.expireTime.Add(time.Duration(token.Expire-60) * time.Second)
	}
	return nil
}
