package lakala

import (
	"bytes"
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

	"hfb_sys/backend/internal/timeutil"
)

var (
	ErrConfigIncomplete = errors.New("lakala payment config incomplete")
	ErrSignFailed       = errors.New("lakala sign failed")
	ErrVerifyFailed     = errors.New("lakala verify failed")
)

const (
	endpointCounterCreate = "/api/v3/ccss/counter/order/special_create"
	endpointCounterQuery  = "/api/v3/ccss/counter/order/query"
	endpointRefund        = "/api/v3/labs/relation/refund"
	endpointRefundQuery   = "/api/v3/labs/query/idmrefundquery"
)

type Config struct {
	GatewayURL         string
	AppID              string
	SerialNo           string
	MerchantID         string
	TermNo             string
	PrivateKey         string
	LakalaCert         string
	NotifyCert         string
	NotifyURL          string
	JumpURL            string
	PayWay             string
	JSPayFlag          string
	PayMode            string
	OrderExpireMinutes int
}

type Client struct {
	cfg        Config
	httpClient *http.Client
	privateKey *rsa.PrivateKey
	lakalaCert *x509.Certificate
	notifyCert *x509.Certificate
}

type CreatePaymentRequest struct {
	ThirdOrderID string
	AmountCent   int64
	PayWay       string
	JSPayFlag    string
	NotifyURL    string
	JumpURL      string
	ClientIP     string
	Body         string
	Attach       string
}

type CreatePaymentResponse struct {
	OK              bool
	ErrorMessage    string
	MerchantID      string
	ThirdOrderID    string
	ProviderOrderID string
	PayWay          string
	Status          string
	PayTime         string
	TDCode          string
	JSPayURL        string
	JSPayInfo       string
	Raw             map[string]string
	RawRequest      map[string]string
}

type QueryPaymentResponse struct {
	OK              bool
	ErrorMessage    string
	MerchantID      string
	ThirdOrderID    string
	ProviderOrderID string
	Status          string
	Amount          string
	PayWay          string
	PayTime         string
	Raw             map[string]string
}

type CreateRefundRequest struct {
	ThirdOrderID     string
	ProviderOrderID  string
	MerchantRefundID string
	RefundAmountCent int64
	NotifyURL        string
	Attach           string
	RefundReason     string
	ClientIP         string
}

type CreateRefundResponse struct {
	OK               bool
	ErrorMessage     string
	MerchantID       string
	ThirdOrderID     string
	ProviderOrderID  string
	MerchantRefundID string
	ProviderRefundID string
	Status           string
	RefundAmount     string
	Raw              map[string]string
	RawRequest       map[string]string
}

type QueryRefundRequest struct {
	ThirdOrderID     string
	ProviderOrderID  string
	MerchantRefundID string
	ProviderRefundID string
}

type QueryRefundResponse struct {
	OK               bool
	ErrorMessage     string
	MerchantID       string
	ThirdOrderID     string
	ProviderOrderID  string
	MerchantRefundID string
	ProviderRefundID string
	Status           string
	RefundAmount     string
	RefundTime       string
	Raw              map[string]string
}

type VerifyNotifyResult struct {
	OK         bool
	MatchedKey string
	Got        string
	Expected   map[string]string
	BaseString map[string]string
	ParamKeys  []string
}

func NewClient(cfg Config) *Client {
	return &Client{
		cfg: cfg,
		httpClient: &http.Client{
			Timeout: 15 * time.Second,
		},
	}
}

func (c *Client) CreatePayment(ctx context.Context, req CreatePaymentRequest) (*CreatePaymentResponse, error) {
	if err := c.initCrypto(); err != nil {
		return nil, err
	}
	payWay := firstNonEmpty(req.PayWay, c.cfg.PayWay, "ZFBZF")
	payMode := normalizeCounterPayMode(c.cfg.PayMode)
	expireMinutes := c.cfg.OrderExpireMinutes
	if expireMinutes <= 0 {
		expireMinutes = 15
	}
	now := timeutil.ShanghaiNow()
	reqData := map[string]any{
		"out_order_no":         req.ThirdOrderID,
		"merchant_no":          c.cfg.MerchantID,
		"total_amount":         req.AmountCent,
		"order_efficient_time": now.Add(time.Duration(expireMinutes) * time.Minute).Format("20060102150405"),
		"notify_url":           firstNonEmpty(req.NotifyURL, c.cfg.NotifyURL),
		"support_refund":       1,
		"support_repeat_pay":   1,
		"support_cancel":       0,
		"order_info":           sanitizeText(req.Body, 128),
	}
	if payMode != "" {
		reqData["counter_param"] = fmt.Sprintf(`{"pay_mode":"%s"}`, payMode)
	}
	if c.cfg.TermNo != "" {
		reqData["term_no"] = c.cfg.TermNo
	}
	if jumpURL := firstNonEmpty(req.JumpURL, c.cfg.JumpURL); jumpURL != "" {
		reqData["callback_url"] = jumpURL
	}
	if req.Attach != "" {
		reqData["counter_remark"] = sanitizeText(req.Attach, 128)
	}
	rawReq := stringifyMap(reqData)
	raw, err := c.post(ctx, endpointCounterCreate, reqData)
	if err != nil {
		return nil, err
	}
	return &CreatePaymentResponse{
		OK:              responseOK(raw),
		ErrorMessage:    responseMessage(raw),
		MerchantID:      firstNonEmpty(raw["merchant_no"], c.cfg.MerchantID),
		ThirdOrderID:    firstNonEmpty(raw["out_order_no"], req.ThirdOrderID),
		ProviderOrderID: firstNonEmpty(raw["pay_order_no"], raw["trade_no"], raw["log_no"]),
		PayWay:          payWay,
		Status:          normalizePaymentStatus(raw),
		PayTime:         firstNonEmpty(raw["pay_time"], raw["trade_time"], raw["finish_time"]),
		TDCode:          firstNonEmpty(raw["qr_code"], raw["code_url"], raw["pay_url"], raw["counter_url"]),
		JSPayURL:        firstNonEmpty(raw["counter_url"], raw["pay_url"], raw["qr_code"], raw["code_url"]),
		JSPayInfo:       firstNonEmpty(raw["pay_info"], raw["credential"], raw["credential_json"], raw["req_data"]),
		Raw:             raw,
		RawRequest:      rawReq,
	}, nil
}

func (c *Client) QueryPayment(ctx context.Context, thirdOrderID, providerOrderID string) (*QueryPaymentResponse, error) {
	if err := c.initCrypto(); err != nil {
		return nil, err
	}
	reqData := map[string]any{
		"merchant_no": c.cfg.MerchantID,
	}
	if providerOrderID != "" {
		reqData["pay_order_no"] = providerOrderID
	} else {
		reqData["out_order_no"] = thirdOrderID
	}
	raw, err := c.post(ctx, endpointCounterQuery, reqData)
	if err != nil {
		return nil, err
	}
	return &QueryPaymentResponse{
		OK:              responseOK(raw),
		ErrorMessage:    responseMessage(raw),
		MerchantID:      firstNonEmpty(raw["merchant_no"], c.cfg.MerchantID),
		ThirdOrderID:    firstNonEmpty(raw["out_order_no"], thirdOrderID),
		ProviderOrderID: firstNonEmpty(raw["pay_order_no"], raw["trade_no"], providerOrderID),
		Status:          normalizePaymentStatus(raw),
		Amount:          firstNonEmpty(raw["total_amount"], raw["amount"], raw["trade_amount"]),
		PayWay:          firstNonEmpty(raw["pay_mode"], raw["account_type"]),
		PayTime:         firstNonEmpty(raw["pay_time"], raw["trade_time"], raw["finish_time"]),
		Raw:             raw,
	}, nil
}

func (c *Client) CreateRefund(ctx context.Context, req CreateRefundRequest) (*CreateRefundResponse, error) {
	if err := c.initCrypto(); err != nil {
		return nil, err
	}
	reqData := map[string]any{
		"merchant_no":         c.cfg.MerchantID,
		"term_no":             c.cfg.TermNo,
		"out_trade_no":        req.MerchantRefundID,
		"refund_amount":       strconv.FormatInt(req.RefundAmountCent, 10),
		"refund_reason":       firstNonEmpty(req.RefundReason, "订单退款"),
		"origin_out_trade_no": req.ThirdOrderID,
		"origin_trade_no":     req.ProviderOrderID,
		"location_info":       defaultLocationInfo(req.ClientIP),
	}
	if c.cfg.TermNo == "" {
		delete(reqData, "term_no")
	}
	if req.ProviderOrderID == "" {
		delete(reqData, "origin_trade_no")
	}
	rawReq := stringifyMap(reqData)
	raw, err := c.post(ctx, endpointRefund, reqData)
	if err != nil {
		return nil, err
	}
	return &CreateRefundResponse{
		OK:               responseOK(raw),
		ErrorMessage:     responseMessage(raw),
		MerchantID:       firstNonEmpty(raw["merchant_no"], c.cfg.MerchantID),
		ThirdOrderID:     firstNonEmpty(raw["origin_out_trade_no"], req.ThirdOrderID),
		ProviderOrderID:  firstNonEmpty(raw["origin_trade_no"], req.ProviderOrderID),
		MerchantRefundID: firstNonEmpty(raw["out_trade_no"], req.MerchantRefundID),
		ProviderRefundID: firstNonEmpty(raw["refund_trade_no"], raw["trade_no"], raw["log_no"]),
		Status:           normalizeRefundStatus(raw),
		RefundAmount:     firstNonEmpty(raw["refund_amount"], strconv.FormatInt(req.RefundAmountCent, 10)),
		Raw:              raw,
		RawRequest:       rawReq,
	}, nil
}

func (c *Client) QueryRefund(ctx context.Context, req QueryRefundRequest) (*QueryRefundResponse, error) {
	if err := c.initCrypto(); err != nil {
		return nil, err
	}
	reqData := map[string]any{
		"merchant_no": c.cfg.MerchantID,
		"term_no":     c.cfg.TermNo,
	}
	if req.MerchantRefundID != "" {
		reqData["out_trade_no"] = req.MerchantRefundID
	}
	if req.ThirdOrderID != "" {
		reqData["origin_out_trade_no"] = req.ThirdOrderID
	}
	if req.ProviderOrderID != "" {
		reqData["origin_trade_no"] = req.ProviderOrderID
	}
	if c.cfg.TermNo == "" {
		delete(reqData, "term_no")
	}
	raw, err := c.post(ctx, endpointRefundQuery, reqData)
	if err != nil {
		return nil, err
	}
	return &QueryRefundResponse{
		OK:               responseOK(raw),
		ErrorMessage:     responseMessage(raw),
		MerchantID:       firstNonEmpty(raw["merchant_no"], c.cfg.MerchantID),
		ThirdOrderID:     firstNonEmpty(raw["origin_out_trade_no"], req.ThirdOrderID),
		ProviderOrderID:  firstNonEmpty(raw["origin_trade_no"], req.ProviderOrderID),
		MerchantRefundID: firstNonEmpty(raw["out_trade_no"], req.MerchantRefundID),
		ProviderRefundID: firstNonEmpty(raw["refund_trade_no"], raw["trade_no"], req.ProviderRefundID),
		Status:           normalizeRefundStatus(raw),
		RefundAmount:     raw["refund_amount"],
		RefundTime:       firstNonEmpty(raw["refund_time"], raw["trade_time"], raw["finish_time"]),
		Raw:              raw,
	}, nil
}

func (c *Client) VerifyNotifyDetail(rawPayload string, authorization string) VerifyNotifyResult {
	result := VerifyNotifyResult{
		Expected:   map[string]string{},
		BaseString: map[string]string{},
	}
	if err := c.initCrypto(); err != nil {
		result.Expected["error"] = err.Error()
		return result
	}
	signature, base, err := parseAuthorization(authorization)
	result.Got = signature
	result.BaseString["notify_cert"] = base.message(rawPayload)
	if err != nil {
		result.Expected["error"] = err.Error()
		return result
	}
	ok := verifyRSA(c.notifyCert, []byte(base.message(rawPayload)), signature)
	result.OK = ok
	if ok {
		result.MatchedKey = "notify_cert"
	}
	return result
}

func ParsePayload(body []byte) (map[string]string, error) {
	var payload any
	if err := json.Unmarshal(body, &payload); err != nil {
		return nil, err
	}
	raw := flattenJSON(payload)
	normalizeNotifyAliases(raw)
	return raw, nil
}

func (c *Client) post(ctx context.Context, endpoint string, reqData map[string]any) (map[string]string, error) {
	body, err := json.Marshal(map[string]any{
		"req_time": timeutil.ShanghaiNow().Format("20060102150405"),
		"version":  "3.0",
		"req_data": reqData,
	})
	if err != nil {
		return nil, err
	}
	url := strings.TrimRight(strings.TrimSpace(c.cfg.GatewayURL), "/") + endpoint
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", c.authorization(string(body)))
	req.Header.Set("lkl-op-sdk", "hfb-go-lakala")
	req.Header.Set("lkl-op-flowgroup", "NORMAL")
	req.Header.Set("lkl-op-appid", c.cfg.AppID)
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBody, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
	if err != nil {
		return nil, err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("lakala http status %d: %s", resp.StatusCode, string(respBody))
	}
	if err := c.verifyResponse(resp.Header, string(respBody)); err != nil {
		return nil, err
	}
	var payload any
	if len(respBody) == 0 {
		return map[string]string{}, nil
	}
	if err := json.Unmarshal(respBody, &payload); err != nil {
		return nil, err
	}
	raw := flattenJSON(payload)
	normalizeNotifyAliases(raw)
	return raw, nil
}

func (c *Client) authorization(body string) string {
	nonce := Nonce(32)
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
	message := strings.Join([]string{c.cfg.AppID, c.cfg.SerialNo, timestamp, nonce, body, ""}, "\n")
	signature, err := signRSA(c.privateKey, []byte(message))
	if err != nil {
		return ""
	}
	return fmt.Sprintf(
		`LKLAPI-SHA256withRSA appid="%s",nonce_str="%s",timestamp="%s",serial_no="%s",signature="%s"`,
		c.cfg.AppID,
		nonce,
		timestamp,
		c.cfg.SerialNo,
		signature,
	)
}

func (c *Client) verifyResponse(header http.Header, body string) error {
	signature := header.Get("Lklapi-Signature")
	if signature == "" {
		return nil
	}
	appID := header.Get("Lklapi-Appid")
	if appID == "" {
		appID = c.cfg.AppID
	}
	serial := header.Get("Lklapi-Serial")
	timestamp := header.Get("Lklapi-Timestamp")
	nonce := header.Get("Lklapi-Nonce")
	if serial == "" || timestamp == "" || nonce == "" {
		return ErrVerifyFailed
	}
	message := strings.Join([]string{appID, serial, timestamp, nonce, body, ""}, "\n")
	if !verifyRSA(c.lakalaCert, []byte(message), signature) {
		return ErrVerifyFailed
	}
	return nil
}

func (c *Client) initCrypto() error {
	if strings.TrimSpace(c.cfg.GatewayURL) == "" ||
		strings.TrimSpace(c.cfg.AppID) == "" ||
		strings.TrimSpace(c.cfg.SerialNo) == "" ||
		strings.TrimSpace(c.cfg.MerchantID) == "" ||
		strings.TrimSpace(c.cfg.PrivateKey) == "" ||
		strings.TrimSpace(c.cfg.NotifyCert) == "" {
		return ErrConfigIncomplete
	}
	if c.privateKey == nil {
		key, err := parsePrivateKey(c.cfg.PrivateKey)
		if err != nil {
			return err
		}
		c.privateKey = key
	}
	if c.notifyCert == nil {
		cert, err := parseCertificate(c.cfg.NotifyCert)
		if err != nil {
			return err
		}
		c.notifyCert = cert
	}
	if c.lakalaCert == nil && c.cfg.LakalaCert != "" {
		cert, err := parseCertificate(c.cfg.LakalaCert)
		if err != nil {
			return err
		}
		c.lakalaCert = cert
	}
	if c.lakalaCert == nil {
		c.lakalaCert = c.notifyCert
	}
	return nil
}

type authorizationBase struct {
	timestamp string
	nonce     string
}

func (b authorizationBase) message(body string) string {
	return strings.Join([]string{b.timestamp, b.nonce, body, ""}, "\n")
}

func parseAuthorization(value string) (string, authorizationBase, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return "", authorizationBase{}, errors.New("empty authorization")
	}
	space := strings.Index(value, " ")
	if space < 0 {
		return "", authorizationBase{}, errors.New("invalid authorization")
	}
	items := strings.Split(value[space+1:], ",")
	params := map[string]string{}
	for _, item := range items {
		parts := strings.SplitN(strings.TrimSpace(item), "=", 2)
		if len(parts) != 2 {
			continue
		}
		params[parts[0]] = strings.Trim(parts[1], `"`)
	}
	signature := params["signature"]
	base := authorizationBase{
		timestamp: params["timestamp"],
		nonce:     params["nonce_str"],
	}
	if signature == "" || base.timestamp == "" || base.nonce == "" {
		return signature, base, errors.New("authorization missing signature fields")
	}
	return signature, base, nil
}

func parsePrivateKey(value string) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode([]byte(value))
	if block == nil {
		return nil, errors.New("invalid private key pem")
	}
	if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
		return key, nil
	}
	parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	key, ok := parsed.(*rsa.PrivateKey)
	if !ok {
		return nil, errors.New("private key is not rsa")
	}
	return key, nil
}

func parseCertificate(value string) (*x509.Certificate, error) {
	block, _ := pem.Decode([]byte(value))
	if block == nil {
		return nil, errors.New("invalid certificate pem")
	}
	return x509.ParseCertificate(block.Bytes)
}

func signRSA(key *rsa.PrivateKey, message []byte) (string, error) {
	if key == nil {
		return "", ErrSignFailed
	}
	digest := sha256.Sum256(message)
	signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, digest[:])
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(signature), nil
}

func verifyRSA(cert *x509.Certificate, message []byte, signature string) bool {
	if cert == nil || signature == "" {
		return false
	}
	sig, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return false
	}
	pub, ok := cert.PublicKey.(*rsa.PublicKey)
	if !ok {
		return false
	}
	digest := sha256.Sum256(message)
	return rsa.VerifyPKCS1v15(pub, crypto.SHA256, digest[:], sig) == nil
}

func normalizeNotifyAliases(raw map[string]string) {
	raw["third_order_id"] = firstNonEmpty(raw["third_order_id"], raw["out_order_no"], raw["out_trade_no"])
	raw["provider_order_id"] = firstNonEmpty(raw["provider_order_id"], raw["pay_order_no"], raw["trade_no"], raw["log_no"])
	raw["amount"] = firstNonEmpty(raw["amount"], raw["total_amount"], raw["trade_amount"])
	raw["status"] = normalizePaymentStatus(raw)
	raw["pay_time"] = firstNonEmpty(raw["pay_time"], raw["trade_time"], raw["finish_time"])
	if raw["refund_amount"] != "" || raw["origin_out_trade_no"] != "" {
		raw["merchant_refund_id"] = firstNonEmpty(raw["merchant_refund_id"], raw["out_trade_no"])
		raw["provider_refund_id"] = firstNonEmpty(raw["provider_refund_id"], raw["refund_trade_no"], raw["trade_no"], raw["log_no"])
		raw["status"] = normalizeRefundStatus(raw)
	}
}

func normalizePaymentStatus(raw map[string]string) string {
	value := strings.ToUpper(firstNonEmpty(raw["trade_state"], raw["order_status"], raw["trade_status"], raw["status"], raw["pay_status"]))
	switch value {
	case "SUCCESS", "PAY_SUCCESS", "TRADE_SUCCESS", "PAID", "S", "2", "30":
		return "paid"
	case "CLOSED", "CLOSE", "CANCEL", "CANCELED", "CANCELLED", "6":
		return "closed"
	case "FAIL", "FAILED", "PAY_FAIL", "TRADE_FAIL", "F", "8":
		return "failed"
	case "REFUND", "REFUNDED":
		return "refunded"
	default:
		return "paying"
	}
}

func normalizeRefundStatus(raw map[string]string) string {
	value := strings.ToUpper(firstNonEmpty(raw["refund_status"], raw["trade_state"], raw["trade_status"], raw["status"]))
	switch value {
	case "SUCCESS", "REFUND_SUCCESS", "TRADE_SUCCESS", "REFUNDED", "S", "11":
		return "refunded"
	case "FAIL", "FAILED", "REFUND_FAIL", "TRADE_FAIL", "F", "12":
		return "failed"
	}
	if hasSuccessResponseCode(raw) {
		return "refunded"
	}
	return "refunding"
}

func hasSuccessResponseCode(raw map[string]string) bool {
	code := firstNonEmpty(raw["code"], raw["resp_code"], raw["result_code"], raw["return_code"])
	return code != "" && responseOK(raw)
}

func responseOK(raw map[string]string) bool {
	code := strings.ToUpper(firstNonEmpty(raw["code"], raw["resp_code"], raw["result_code"], raw["return_code"]))
	if code == "" {
		return true
	}
	return code == "0" || code == "000000" || code == "SUCCESS" || code == "BBS00000"
}

func responseMessage(raw map[string]string) string {
	return firstNonEmpty(raw["message"], raw["msg"], raw["error_msg"], raw["resp_msg"], raw["return_msg"])
}

func flattenJSON(value any) map[string]string {
	out := map[string]string{}
	var walk func(prefix string, v any)
	walk = func(prefix string, v any) {
		switch item := v.(type) {
		case map[string]any:
			for key, child := range item {
				if key == "req_data" || key == "resp_data" || key == "data" {
					walk("", child)
					continue
				}
				walk(key, child)
			}
		case []any:
			raw, _ := json.Marshal(item)
			if prefix != "" {
				out[prefix] = string(raw)
			}
		case nil:
			if prefix != "" {
				out[prefix] = ""
			}
		case string:
			if prefix != "" {
				out[prefix] = item
			}
		case float64:
			if prefix != "" {
				if math.Trunc(item) == item {
					out[prefix] = strconv.FormatInt(int64(item), 10)
				} else {
					out[prefix] = strconv.FormatFloat(item, 'f', -1, 64)
				}
			}
		case bool:
			if prefix != "" {
				out[prefix] = strconv.FormatBool(item)
			}
		default:
			if prefix != "" {
				raw, _ := json.Marshal(item)
				out[prefix] = string(raw)
			}
		}
	}
	walk("", value)
	return out
}

func stringifyMap(value map[string]any) map[string]string {
	out := map[string]string{}
	for key, item := range value {
		switch typed := item.(type) {
		case string:
			out[key] = typed
		default:
			raw, _ := json.Marshal(typed)
			out[key] = string(raw)
		}
	}
	return out
}

func defaultLocationInfo(clientIP string) map[string]string {
	info := map[string]string{
		"request_ip": firstNonEmpty(clientIP, "127.0.0.1"),
	}
	return info
}

func payModeFromPayWay(payWay string) string {
	switch strings.ToUpper(payWay) {
	case "WXZF", "WECHAT", "WECHATPAY":
		return "WECHAT"
	case "UNIONPAY", "UQRCODEPAY":
		return "UQRCODEPAY"
	default:
		return "ALIPAY"
	}
}

func normalizeCounterPayMode(value string) string {
	switch strings.ToUpper(strings.TrimSpace(value)) {
	case "", "AUTO", "ALL", "NONE", "UNLIMITED":
		return ""
	default:
		return strings.ToUpper(strings.TrimSpace(value))
	}
}

func sanitizeText(value string, maxRunes int) string {
	value = strings.TrimSpace(value)
	if value == "" || maxRunes <= 0 {
		return value
	}
	runes := []rune(value)
	if len(runes) <= maxRunes {
		return value
	}
	return string(runes[:maxRunes])
}

func Nonce(length int) string {
	const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	if length <= 0 {
		length = 32
	}
	buf := make([]byte, length)
	if _, err := rand.Read(buf); err != nil {
		return strconv.FormatInt(time.Now().UnixNano(), 36)
	}
	for i := range buf {
		buf[i] = chars[int(buf[i])%len(chars)]
	}
	return string(buf)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}
