package v1

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/marmotedu/errors"
	"github.com/pingcap-inc/pinsights/internal/apiserver/store"
	"github.com/pingcap-inc/pinsights/internal/pkg/code"
	"github.com/pingcap-inc/pinsights/pkg/log"
	"github.com/pingcap-inc/pinsights/pkg/storage"
	"github.com/pingcap-inc/pinsights/pkg/util/jsonc"
	"github.com/spf13/viper"
)

type larkCommonService struct{}

type LarkCommonSrv interface {
	GetAndCacheP7STToken(ctx context.Context) (string, error)
}

type LarkTATokenRes struct {
	Code   int64  `json:"code"`
	Msg    string `json:"msg"`
	TAT    string `json:"tenant_access_token"`
	Expire int    `json:"expire"`
}

type LarkCommonRes struct {
	Code int64           `json:"code"`
	Msg  string          `json:"msg"`
	Data json.RawMessage `json:"data"`
}

const TENANT_TOKEN_KEY = "p7s-tenant-token"
const LARK_OPEN_API_BASE = "https://open.feishu.cn/open-apis"

func newLarkCommon() *larkCommonService {
	return &larkCommonService{}
}

func (lc *larkCommonService) GetAndCacheP7STToken(ctx context.Context) (string, error) {
	p7sAppIdInLark := viper.GetString("lark.appId")
	p7sAppSecInLark := viper.GetString("lark.appSecret")
	if p7sAppIdInLark == "" || p7sAppSecInLark == "" {
		log.Error("There is no appId or appSecret for p7s-lark-app")
		return "", errors.WithCode(code.ErrNoLarkAppIdSecret, "")
	}

	rStore := &storage.RedisCluster{
		KeyPrefix: store.LarkKeyPrefix,
	}

	val, err := rStore.GetKey(TENANT_TOKEN_KEY)
	if err != nil {
		if !errors.As(err, &storage.ErrKeyNotFound) {
			log.Error(errors.Wrap(err, "error when retrieve key in redis").Error())
		}
	}

	tToken := val
	if val == "" {
		log.Info("P7S tenant access token not found in redis. Try to get from api")
		tatBody, _ := json.Marshal(map[string]string{
			"app_id":     p7sAppIdInLark,
			"app_secret": p7sAppSecInLark,
		})
		tatReqBody := bytes.NewBuffer(tatBody)
		tatResp, err := http.Post(LARK_OPEN_API_BASE+"/auth/v3/tenant_access_token/internal", "application/json", tatReqBody)
		if err != nil {
			log.Error(err.Error())
			return "", errors.Wrap(err, "error when request lark api for tenant_access_toekn")
		}
		defer tatResp.Body.Close()
		tatRespBody, _ := ioutil.ReadAll(tatResp.Body)
		var tatRes LarkTATokenRes
		if err := json.Unmarshal(tatRespBody, &tatRes); err != nil {
			log.Errorf("Error when unmarshal lark res: %s", err.Error())
			return "", errors.Wrap(err, "error when unmarshal lark tenant_access_token res")
		}

		tToken = tatRes.TAT

		if err := rStore.SetKey(TENANT_TOKEN_KEY, tToken, time.Duration(tatRes.Expire)*time.Second-time.Minute*10); err != nil {
			log.Errorf("Error when store token into redis: %s", err.Error())
		}
	} else {
		log.Info("P7S tenant access token found in redis. Use it from redis.")
	}

	return tToken, nil
}

func (lc *larkCommonService) GetDataByReqLarkAPI(method string, url string, body io.Reader) (json.RawMessage, error) {
	tat, err := lc.GetAndCacheP7STToken(nil)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when GetAndCacheP7STToken")
	}

	req, err := http.NewRequest(method, url, body)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when new request lark api: "+method+" "+url)
	}
	req.Header.Set("Authorization", "Bearer "+tat)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when request lark api: "+method+" "+url)
	}
	defer resp.Body.Close()

	b, _ := ioutil.ReadAll(resp.Body)
	var res LarkCommonRes
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel(b), &res); err != nil {
		log.Error(err.Error())
		return nil, errors.Wrap(err, "Error when unmarshal lark api res: "+method+" "+url)
	}
	if res.Code != 0 {
		log.Errorf("Lark API error code: %d, msg: %s", res.Code, res.Msg)
		return nil, errors.WithCode(code.ErrLarkApiBizErrorCode, "Lark Error Code: %d", res.Code)
	}

	return res.Data, nil
}
