package yilianyunapi

import (
	"crypto/md5"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"git.rosy.net.cn/baseapi/platformapi"
	"git.rosy.net.cn/baseapi/utils"
)

const (
	prodURL              = "https://open-api.10ss.net"
	signKey              = "sign"
	timestampKey         = "timestamp"
	oathApiPath          = "oauth/oauth"
	scanCodemodelApiPath = "oauth/scancodemodel"
)

const (
	// ResponseCodeSuccess 操作成功
	ResponseCodeSuccess = "0"
)

const (
	PrintStatusOffline   = 0
	PrintStatusOnline    = 1
	PrintStatusLackPaper = 2
)

var (
	exceedLimitCodes = map[string]int{}
	canRetryCodes    = map[string]int{}
)

type API struct {
	locker      sync.RWMutex
	accessToken string

	clientID     string
	clientSecret string
	client       *http.Client
	config       *platformapi.APIConfig
}

type TokenInfo struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresIn    int64  `json:"expires_in"`

	Scope       string `json:"scope"`
	MachineCode string `json:"machine_code"`
}

type PrinterOrderResult struct {
	Content       string `json:"content"`
	ID            string `json:"id"`
	OriginID      string `json:"origin_id"`
	PrintTime     string `json:"print_time"`
	ReceivingTime string `json:"receiving_time"`
	Status        int    `json:"status"`
}

func New(clientID, clientSecret string, config ...*platformapi.APIConfig) *API {
	curConfig := platformapi.DefAPIConfig
	if len(config) > 0 {
		curConfig = *config[0]
	}
	return &API{
		clientID:     clientID,
		clientSecret: clientSecret,
		client:       &http.Client{Timeout: curConfig.ClientTimeout},
		config:       &curConfig,
	}
}

func (a *API) signParams(timestamp int64) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(a.clientID+utils.Int64ToStr(timestamp)+a.clientSecret)))
}

func (a *API) AccessAPI(apiName string, apiParams map[string]interface{}, token string) (retVal map[string]interface{}, err error) {
	if !IsStrToken(token) {
		token = ""
	}
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			timestamp := time.Now().Unix()
			params := utils.MergeMaps(map[string]interface{}{
				timestampKey: timestamp,
				signKey:      a.signParams(timestamp),
				"client_id":  a.clientID,
				"id":         utils.GetUpperUUID(),
			}, apiParams)
			if oathApiPath != apiName && scanCodemodelApiPath != apiName {
				if token == "" {
					token = a.GetToken()
				}
				params["access_token"] = token
			}
			request, _ := http.NewRequest(http.MethodPost, utils.GenerateGetURL(prodURL, apiName, nil), strings.NewReader(utils.Map2URLValues(params).Encode()))
			request.Header.Set("charset", "UTF-8")
			request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

			// request.Close = true //todo 为了性能考虑还是不要关闭
			return request
		},
		a.config,
		func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
			if jsonResult1 == nil {
				return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
			}
			code := utils.Interface2String(jsonResult1["error"])
			if code == ResponseCodeSuccess {
				retVal, _ = jsonResult1["body"].(map[string]interface{})
				return platformapi.ErrLevelSuccess, nil
			}
			newErr := utils.NewErrorCode(jsonResult1["error_description"].(string), code)
			if _, ok := exceedLimitCodes[code]; ok {
				return platformapi.ErrLevelExceedLimit, newErr
			} else if _, ok := canRetryCodes[code]; ok {
				return platformapi.ErrLevelRecoverableErr, newErr
			} else {
				return platformapi.ErrLevelCodeIsNotOK, newErr
			}
		})
	return retVal, err
}

func (a *API) SetToken(newToken string) bool {
	curToken := a.GetToken()
	if curToken != newToken {
		a.locker.Lock()
		defer a.locker.Unlock()
		a.accessToken = newToken
		return true
	}
	return false
}

func (a *API) GetToken() string {
	a.locker.RLock()
	defer a.locker.RUnlock()
	return a.accessToken
}

func (a *API) RetrieveToken() (tokenInfo *TokenInfo, err error) {
	result, err := a.AccessAPI(oathApiPath, map[string]interface{}{
		"grant_type": "client_credentials",
		"scope":      "all",
	}, "")
	if err == nil {
		if err = utils.Map2StructByJson(result, &tokenInfo, false); err == nil {
			a.SetToken(tokenInfo.AccessToken)
		}
	}
	return tokenInfo, err
}

func (a *API) Code2Token(code string) (tokenInfo *TokenInfo, err error) {
	result, err := a.AccessAPI(oathApiPath, map[string]interface{}{
		"grant_type": "authorization_code",
		"scope":      "all",
		"code":       code,
	}, "")
	if err == nil {
		err = utils.Map2StructByJson(result, &tokenInfo, false)
	}
	return tokenInfo, err
}

func (a *API) RefreshToken(refreshToken string) (tokenInfo *TokenInfo, err error) {
	result, err := a.AccessAPI(oathApiPath, map[string]interface{}{
		"grant_type":    "refresh_token",
		"scope":         "all",
		"refresh_token": refreshToken,
	}, "")
	if err == nil {
		err = utils.Map2StructByJson(result, &tokenInfo, false)
	}
	return tokenInfo, err
}

func (a *API) AddPrinter(machineCode, msign, printerName string) (err error) {
	_, err = a.AccessAPI("printer/addprinter", map[string]interface{}{
		"machine_code": machineCode,
		"msign":        msign,
		"print_name":   printerName,
	}, "")
	return err
}

func (a *API) DeletePrinter(machineCode string) (err error) {
	_, err = a.AccessAPI("printer/deleteprinter", map[string]interface{}{
		"machine_code": machineCode,
	}, "")
	return err
}

func (a *API) printMsg(machineCode, orderID, content, token string) (err error) {
	if orderID == "" {
		orderID = utils.GetUUID()
	}
	_, err = a.AccessAPI("print/index", map[string]interface{}{
		"machine_code": machineCode,
		"origin_id":    orderID,
		"content":      content,
	}, token)
	return err
}

func (a *API) PrintMsg(machineCode, orderID, content string) (err error) {
	return a.printMsg(machineCode, orderID, content, "")
}

func (a *API) PrintMsgWithToken(machineCode, orderID, content, token string) (err error) {
	return a.printMsg(machineCode, orderID, content, token)
}

func (a *API) getPrintStatus(machineCode, token string) (state int, err error) {
	result, err := a.AccessAPI("printer/getprintstatus", map[string]interface{}{
		"machine_code": machineCode,
	}, token)
	if err != nil {
		return 0, err
	}
	return int(utils.Str2Int64(utils.Interface2String(result["state"]))), nil
}

func (a *API) GetPrintStatus(machineCode string) (state int, err error) {
	return a.getPrintStatus(machineCode, "")
}

func (a *API) GetPrintStatusWithToken(machineCode, token string) (state int, err error) {
	return a.getPrintStatus(machineCode, token)
}

func (a *API) GetPrinterToken(machineCode, qrKey, msign string) (tokenInfo *TokenInfo, err error) {
	parameter := map[string]interface{}{
		"machine_code": machineCode,
		"scope":        "all",
	}
	if qrKey != "" {
		parameter["qr_key"] = qrKey
	} else if msign != "" {
		parameter["msign"] = msign
	}

	result, err := a.AccessAPI(scanCodemodelApiPath, parameter, "")
	if err == nil {
		err = utils.Map2StructByJson(result, &tokenInfo, false)
	}
	return tokenInfo, err
}

func IsStrToken(possibleToken string) bool {
	return len(possibleToken) >= len("d65578a6fa414d738e0c44f85ac4b950")
}

func (a *API) CancelAll(machineCode, token string) (err error) {
	_, err = a.AccessAPI("printer/cancelall", map[string]interface{}{
		"machine_code": machineCode,
	}, token)
	return err
}

func (a *API) PlayText(machineCode, orderID, text, token string) (err error) {
	return a.printMsg(machineCode, orderID, fmt.Sprintf("<audio>%s,9,0</audio>", strings.Replace(text, ",", ".", -1)), token)
}

// pageIndex从1开始，pageSize最大为100
func (a *API) GetOrderPagingList(machineCode, token string, pageIndex, pageSize int) (orderResultList []*PrinterOrderResult, err error) {
	result, err := a.AccessAPI("printer/getorderpaginglist", map[string]interface{}{
		"machine_code": machineCode,
		"page_index":   pageIndex,
		"page_size":    pageSize,
	}, token)
	if err != nil {
		err = utils.Map2StructByJson(result, &orderResultList, false)
	}
	return orderResultList, err
}

// 设置打印机声音
func (a *API) SetSound(machineCode, voice string) (err error) {
	_, err = a.AccessAPI("printer/setsound", map[string]interface{}{
		"machine_code":  machineCode,
		"response_type": "horn", //蜂鸣器buzzer或喇叭，易连云默认喇叭
		"voice":         voice,
	}, "")
	return err
}
