package credential

import (
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/silenceper/wechat/v2/cache"
	"github.com/silenceper/wechat/v2/util"
)

// getTicketURL 获取ticket的url
const getTicketURL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi"

var (
	jsTicketLock       sync.RWMutex
	jsTicketUpdateChan = make(chan int64, 1)
)

// DefaultJsTicket 默认获取js ticket方法
type DefaultJsTicket struct {
	appID          string
	cacheKeyPrefix string
	cache          cache.Cache
}

// NewDefaultJsTicket new
func NewDefaultJsTicket(appID string, cacheKeyPrefix string, cache cache.Cache) JsTicketHandle {
	return &DefaultJsTicket{
		appID:          appID,
		cache:          cache,
		cacheKeyPrefix: cacheKeyPrefix,
	}
}

// ResTicket 请求jsapi_tikcet返回结果
type ResTicket struct {
	util.CommonError

	Ticket    string `json:"ticket"`
	ExpiresIn int64  `json:"expires_in"`
}

func (js *DefaultJsTicket) getJsTicketFromCache(key string) (string, bool) {
	jsTicketLock.RLock()
	defer jsTicketLock.RUnlock()
	if val := js.cache.Get(key); val != nil {
		switch _t := val.(type) {
		case string:
			if _t != "" {
				return _t, true
			}
		}
	}
	return "", false
}

func (js *DefaultJsTicket) getJsTicketFromSrc(key, accessToken string) (string, error) {
	//从微信服务器获取
	jsTicketLock.Lock()
	defer jsTicketLock.Unlock()
	_ticketInfo, _err := GetTicketFromServer(accessToken)
	if _err != nil {
		return "", _err
	}
	_expires := _ticketInfo.ExpiresIn - 1500
	_ticket := _ticketInfo.Ticket
	js.cache.Set(key, _ticket, time.Duration(_expires)*time.Second)
	return _ticket, nil
}

func (js *DefaultJsTicket) getJsTicketLoop(jsAPITicketCacheKey, accessToken string, loopDepth int) (string, error) {
	// 先从cache中取
	_ticket, _b := js.getJsTicketFromCache(jsAPITicketCacheKey)
	if !_b {
		select {
		case jsTicketUpdateChan <- time.Now().Unix():
			//保证只有一个请求进入，更新access_token
			_token, _err := func() (string, error) {
				defer func() {
					<-jsTicketUpdateChan
				}()
				return js.getJsTicketFromSrc(jsAPITicketCacheKey, accessToken)
			}()
			if _err != nil {
				if loopDepth >= 3 {
					return "", _err
				}
				return js.getJsTicketLoop(jsAPITicketCacheKey, accessToken, loopDepth+1)
			}
			_ticket = _token
		default:
			if loopDepth >= 3 {
				return "", errors.New("access_token update over loop depth times")
			}
			time.Sleep(time.Duration(200) * time.Millisecond)
			return js.getJsTicketLoop(jsAPITicketCacheKey, accessToken, loopDepth+1)
		}
	}
	return _ticket, nil
}

// GetTicket 获取jsapi_ticket
func (js *DefaultJsTicket) GetTicket(accessToken string) (ticketStr string, err error) {
	jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", js.cacheKeyPrefix, js.appID)
	return js.getJsTicketLoop(jsAPITicketCacheKey, accessToken, 0)
}

// GetTicketFromServer 从服务器中获取ticket
func GetTicketFromServer(accessToken string) (ticket ResTicket, err error) {
	var response []byte
	url := fmt.Sprintf(getTicketURL, accessToken)
	response, err = util.HTTPGet(url)
	if err != nil {
		return
	}
	err = json.Unmarshal(response, &ticket)
	if err != nil {
		return
	}
	if ticket.ErrCode != 0 {
		err = fmt.Errorf("getTicket Error : errcode=%d , errmsg=%s", ticket.ErrCode, ticket.ErrMsg)
		return
	}
	return
}
