// Package tencentsig implements Tencent UserSig v2 signing shared by Chat and RTC.
package tencentsig

import (
	"bytes"
	"compress/zlib"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"
)

// Config contains the paid Tencent application credentials required to sign UserSig.
type Config struct {
	// SDKAppID is the application ID assigned by Tencent Cloud.
	SDKAppID int64
	// SecretKey is the application key; it must never leave backend configuration.
	SecretKey string
	// TTL controls how long the generated UserSig remains valid.
	TTL time.Duration
}

// Result is the transport-neutral UserSig output shared by Chat and RTC wrappers.
type Result struct {
	// SDKAppID lets clients initialize the matching Tencent SDK.
	SDKAppID int64 `json:"sdk_app_id"`
	// UserID is the Tencent identifier derived from the immutable internal user_id.
	UserID string `json:"user_id"`
	// UserSig is the signed credential passed to Tencent SDK login or enterRoom.
	UserSig string `json:"user_sig"`
	// ExpireAtMS lets clients refresh before Tencent rejects the credential.
	ExpireAtMS int64 `json:"expire_at_ms"`
}

// GenerateUserSig signs a Tencent UserSig with the HMAC-SHA256 v2 algorithm.
func GenerateUserSig(cfg Config, userID string, now time.Time) (Result, error) {
	userID = strings.TrimSpace(userID)
	if cfg.SDKAppID <= 0 || cfg.SecretKey == "" || cfg.TTL <= 0 || userID == "" {
		// UserSig protects paid Tencent resources; incomplete config must fail closed.
		return Result{}, fmt.Errorf("tencent usersig config is incomplete")
	}

	current := now.Unix()
	expire := int64(cfg.TTL.Seconds())
	rawSig := signPayload(cfg.SecretKey, userID, cfg.SDKAppID, current, expire)
	payload := map[string]any{
		"TLS.ver":        "2.0",
		"TLS.identifier": userID,
		"TLS.sdkappid":   cfg.SDKAppID,
		"TLS.expire":     expire,
		"TLS.time":       current,
		"TLS.sig":        rawSig,
	}

	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		return Result{}, err
	}

	compressed, err := zlibCompress(payloadBytes)
	if err != nil {
		return Result{}, err
	}

	return Result{
		SDKAppID:   cfg.SDKAppID,
		UserID:     userID,
		UserSig:    encodeTencentBase64(compressed),
		ExpireAtMS: now.Add(cfg.TTL).UnixMilli(),
	}, nil
}

func signPayload(secret string, userID string, sdkAppID int64, current int64, expire int64) string {
	// Tencent v2 signing string is line-based and key names are case-sensitive.
	content := "TLS.identifier:" + userID + "\n" +
		"TLS.sdkappid:" + strconv.FormatInt(sdkAppID, 10) + "\n" +
		"TLS.time:" + strconv.FormatInt(current, 10) + "\n" +
		"TLS.expire:" + strconv.FormatInt(expire, 10) + "\n"

	mac := hmac.New(sha256.New, []byte(secret))
	_, _ = mac.Write([]byte(content))

	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

func zlibCompress(payload []byte) ([]byte, error) {
	var buffer bytes.Buffer
	writer := zlib.NewWriter(&buffer)
	if _, err := writer.Write(payload); err != nil {
		_ = writer.Close()
		return nil, err
	}
	if err := writer.Close(); err != nil {
		return nil, err
	}

	return buffer.Bytes(), nil
}

func encodeTencentBase64(payload []byte) string {
	// Tencent UserSig uses standard base64 with URL-hostile characters remapped.
	encoded := base64.StdEncoding.EncodeToString(payload)
	encoded = strings.ReplaceAll(encoded, "+", "*")
	encoded = strings.ReplaceAll(encoded, "/", "-")
	encoded = strings.ReplaceAll(encoded, "=", "_")

	return encoded
}
