package rtc

import (
	"bytes"
	cr "crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"gowebsocket/common"
	"gowebsocket/controllers"

	// "flag"
	"fmt"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	oh "github.com/ossrs/go-oryx-lib/http"
	ol "github.com/ossrs/go-oryx-lib/logger"
	"github.com/spf13/viper"
	"github.com/twinj/uuid"
)

func CreateUserID(channelID, user string) string {
	var b bytes.Buffer
	b.WriteString(channelID)
	b.WriteString("/")
	b.WriteString(user)

	h := sha256.New()
	if _, err := h.Write([]byte(b.String())); err != nil {
		return BuildRandom(16)
	}

	s := h.Sum(nil)
	uid := hex.EncodeToString(s)
	return uid[:16]
}

func CreateToken(
	appID, appKey, channelID, userID, nonce string, timestamp int64,
) (token string, err error) {
	var b bytes.Buffer
	b.WriteString(appID)
	b.WriteString(appKey)
	b.WriteString(channelID)
	b.WriteString(userID)
	b.WriteString(nonce)
	b.WriteString(fmt.Sprint(timestamp))

	h := sha256.New()
	if _, err = h.Write([]byte(b.String())); err != nil {
		return "", err
	}

	s := h.Sum(nil)
	token = hex.EncodeToString(s)
	return
}

// generate a random string
func BuildRandom(length int) string {
	if length <= 0 {
		return ""
	}

	b := make([]byte, length/2+1)
	_, _ = cr.Read(b)
	s := hex.EncodeToString(b)
	return s[:length]
}

// 获取rtc token，用于rtc推流与拉流服务
func GetToken(c *gin.Context) {
	appID := viper.GetString("rtc.appID")
	appKey := viper.GetString("rtc.appKey")
	gslb := viper.GetString("rtc.gslb")

	r := c.Request
	w := c.Writer
	if appID == "" || appKey == "" || gslb == "" {
		os.Exit(-1)
	}

	ol.Tf(nil, "appid=%v, appkey=%v, gslb=%v", appID, appKey, gslb)
	if o := r.Header.Get("Origin"); o != "" {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET,POST,HEAD,PUT,DELETE,OPTIONS")
		w.Header().Set("Access-Control-Expose-Headers", "Server,Range,Content-Length,Content-Range")
		w.Header().Set("Access-Control-Allow-Headers", "Origin,Range,Accept-Encoding,Referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type")
	}

	// For matched OPTIONS, should directly return without response.
	if r.Method == "OPTIONS" {
		return
	}

	q := r.URL.Query()
	channelID, user := q.Get("room"), q.Get("user")
	ol.Tf(nil, "Request channelId=%v, user=%v, appid=%v", channelID, user, appID)

	if channelID == "" || user == "" {
		oh.WriteError(nil, w, r, errors.New("invalid parameter"))
		return
	}

	userID := CreateUserID(channelID, user)

	// Warning: nonce support the AppKey generated token.
	// the Nonce should be prefix with 'AK-' otherwise the joining verification will failed.
	// eg. nonce: "AK-0464002093ce3dd010cb05356c8b1d0f".

	uuid := uuid.NewV4()
	// if err != nil {
	// 	oh.WriteError(nil, w, r, err)
	// 	return
	// }
	nonce := fmt.Sprintf("AK-%v", uuid)

	// Warning: timestamp is the token expiration time.
	// User can custom defined the expire time of token.
	// eg, Expires in two days. timestamp: 1559890860.
	timestamp := time.Now().Add(48 * time.Hour).Unix()

	token, err := CreateToken(appID, appKey, channelID, userID, nonce, timestamp)
	if err != nil {
		oh.WriteError(nil, w, r, err)
		return
	}
	username := fmt.Sprintf("%s?appid=%s&channel=%s&nonce=%s&timestamp=%d",
		userID, appID, channelID, nonce, timestamp)

	ol.Tf(nil, "Login: appID=%v, appKey=%v, channelID=%v, userID=%v, nonce=%v, "+
		"timestamp=%v, user=%v, userName=%v, token=%v",
		appID, appKey, channelID, userID, nonce, timestamp, user, username, token)

	type TURN struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	type Response struct {
		AppId     string   `json:"appid"`
		UserId    string   `json:"userid"`
		GSLB      []string `json:"gslb"`
		Token     string   `json:"token"`
		Nonce     string   `json:"nonce"`
		Timestamp int64    `json:"timestamp"`
		TURN      *TURN    `json:"turn"`
	}
	oh.WriteData(nil, w, r, &Response{
		appID, userID, []string{gslb}, token,
		nonce, timestamp,
		&TURN{username, token},
	})
	data := gin.H{
		"appID":  appID,
		"userID": userID,
		"gslb":   []string{gslb},
		"token":  token,
		"nonce":  timestamp,
		"TURN":   &TURN{username, token},
	}
	controllers.Response(c, common.OK, "", data)
}
