package gameapi

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	hotgameCompatPlatformCode           = "reyou"
	hotgameCompatProxyDefaultBaseURL    = "https://jvapi.haiyihy.com/go"
	hotgameCompatProxyDefaultTimeout    = 10 * time.Second
	hotgameCompatGetUserInfoOperation   = "getUserInfo"
	hotgameCompatUpdateBalanceOperation = "updateBalance"
)

func (h *Handler) handleHotgameGetUserInfo(writer http.ResponseWriter, request *http.Request) {
	h.handleHotgameCompatCallback(writer, request, hotgameCompatGetUserInfoOperation)
}

func (h *Handler) handleHotgameUpdateBalance(writer http.ResponseWriter, request *http.Request) {
	h.handleHotgameCompatCallback(writer, request, hotgameCompatUpdateBalanceOperation)
}

func (h *Handler) handleHotgameCompatCallback(writer http.ResponseWriter, request *http.Request, operation string) {
	raw, err := io.ReadAll(request.Body)
	if err != nil {
		http.Error(writer, "invalid request body", http.StatusBadRequest)
		return
	}
	if h.tryProxyChatapp3HotgameCallback(writer, request, raw, hotgameCompatPlatformCode, operation) {
		return
	}
	// 非 chatapp3 token 全部继续走 hyapp 自己的 reyou 平台配置，避免 hyapp 自有热游游戏误转到 chatapp3。
	h.handleGameCallbackRaw(writer, request, raw, hotgameCompatPlatformCode, operation)
}

func (h *Handler) tryProxyChatapp3HotgameCallback(writer http.ResponseWriter, request *http.Request, raw []byte, platformCode string, operation string) bool {
	// 热游固定回调地址无法按域名区分 hyapp 和 chatapp3；这里用 token 结构做最小路由键。
	// chatapp3 的用户 token 是 sign.base64(version:userId:sysOrigin:expire:release)，hyapp 当前热游 token 是 JWT 或本服务 session。
	if !strings.EqualFold(strings.TrimSpace(platformCode), hotgameCompatPlatformCode) || !isChatapp3HotgameToken(hotgameCompatCallbackToken(raw)) {
		return false
	}
	h.proxyChatapp3HotgameCallback(writer, request, raw, operation)
	return true
}

func (h *Handler) proxyChatapp3HotgameCallback(writer http.ResponseWriter, request *http.Request, raw []byte, operation string) {
	target, err := hotgameCompatProxyURL(operation, request.URL.RawQuery)
	if err != nil {
		http.Error(writer, "proxy config error", http.StatusBadGateway)
		return
	}
	proxyReq, err := http.NewRequestWithContext(request.Context(), http.MethodPost, target, bytes.NewReader(raw))
	if err != nil {
		http.Error(writer, "proxy request error", http.StatusBadGateway)
		return
	}
	copyHotgameProxyRequestHeaders(proxyReq, request)
	appendHotgameForwardedHeaders(proxyReq, request)

	resp, err := h.hotgameCompatHTTPClient.Do(proxyReq)
	if err != nil {
		http.Error(writer, "proxy upstream error", http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()
	copyHotgameProxyResponseHeaders(writer.Header(), resp.Header)
	writer.WriteHeader(resp.StatusCode)
	_, _ = io.Copy(writer, resp.Body)
}

func hotgameCompatHTTPClient() *http.Client {
	return &http.Client{Timeout: hotgameCompatProxyDefaultTimeout}
}

func hotgameCompatCallbackToken(raw []byte) string {
	var payload map[string]json.RawMessage
	if err := json.Unmarshal(raw, &payload); err != nil {
		return ""
	}
	return hotgameCompatJSONText(payload["token"])
}

func hotgameCompatJSONText(raw json.RawMessage) string {
	text := strings.TrimSpace(string(raw))
	if text == "" || text == "null" {
		return ""
	}
	var decoded string
	if err := json.Unmarshal(raw, &decoded); err == nil {
		return strings.TrimSpace(decoded)
	}
	return text
}

func isChatapp3HotgameToken(token string) bool {
	token = strings.TrimSpace(token)
	if chatapp3HotgameTokenCandidateMatches(token) {
		return true
	}
	if decoded, err := url.QueryUnescape(token); err == nil && strings.TrimSpace(decoded) != token {
		return chatapp3HotgameTokenCandidateMatches(decoded)
	}
	return false
}

func chatapp3HotgameTokenCandidateMatches(token string) bool {
	token = normalizeChatapp3HotgameTokenCandidate(token)
	parts := strings.SplitN(token, ".", 2)
	if len(parts) != 2 || strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" {
		return false
	}
	decoded, err := base64.StdEncoding.DecodeString(parts[1])
	if err != nil {
		return false
	}
	payload := strings.TrimSpace(string(decoded))
	if unescaped, err := url.QueryUnescape(payload); err == nil {
		payload = strings.TrimSpace(unescaped)
	}
	fields := strings.Split(payload, ":")
	if len(fields) != 5 || strings.TrimSpace(fields[0]) == "" || strings.TrimSpace(fields[2]) == "" {
		return false
	}
	userID, userErr := strconv.ParseInt(strings.TrimSpace(fields[1]), 10, 64)
	_, expireErr := strconv.ParseInt(strings.TrimSpace(fields[3]), 10, 64)
	_, releaseErr := strconv.ParseInt(strings.TrimSpace(fields[4]), 10, 64)
	// 这里只判定归属，不判定过期；过期 token 仍必须转给 chatapp3，让原项目按自己的错误码回复热游。
	return userID > 0 && userErr == nil && expireErr == nil && releaseErr == nil
}

func normalizeChatapp3HotgameTokenCandidate(token string) string {
	token = strings.TrimSpace(token)
	if strings.HasPrefix(strings.ToLower(token), "bearer ") {
		token = strings.TrimSpace(token[len("bearer "):])
	}
	parts := strings.SplitN(token, ".", 2)
	if len(parts) != 2 || parts[1] == "" {
		return token
	}
	switch len(parts[1]) % 4 {
	case 2:
		parts[1] += "=="
	case 3:
		parts[1] += "="
	}
	return strings.TrimSpace(parts[0]) + "." + strings.TrimSpace(parts[1])
}

func hotgameCompatProxyURL(operation string, rawQuery string) (string, error) {
	parsed, err := url.Parse(hotgameCompatProxyDefaultBaseURL)
	if err != nil {
		return "", err
	}
	parsed.Path = singleJoiningSlash(parsed.Path, operation)
	if strings.TrimSpace(rawQuery) != "" {
		if parsed.RawQuery == "" {
			parsed.RawQuery = rawQuery
		} else {
			parsed.RawQuery += "&" + rawQuery
		}
	}
	return parsed.String(), nil
}

func singleJoiningSlash(left string, right string) string {
	leftSlash := strings.HasSuffix(left, "/")
	rightSlash := strings.HasPrefix(right, "/")
	switch {
	case leftSlash && rightSlash:
		return left + strings.TrimLeft(right, "/")
	case !leftSlash && !rightSlash:
		return left + "/" + right
	default:
		return left + right
	}
}

func copyHotgameProxyRequestHeaders(dst *http.Request, src *http.Request) {
	for key, values := range src.Header {
		if isHopByHopHeader(key) {
			continue
		}
		for _, value := range values {
			dst.Header.Add(key, value)
		}
	}
	if dst.Header.Get("Content-Type") == "" {
		dst.Header.Set("Content-Type", "application/json")
	}
}

func appendHotgameForwardedHeaders(dst *http.Request, src *http.Request) {
	if host := strings.TrimSpace(src.Host); host != "" {
		dst.Header.Set("X-Forwarded-Host", host)
	}
	if proto := strings.TrimSpace(src.Header.Get("X-Forwarded-Proto")); proto != "" {
		dst.Header.Set("X-Forwarded-Proto", proto)
	} else if src.TLS != nil {
		dst.Header.Set("X-Forwarded-Proto", "https")
	} else {
		dst.Header.Set("X-Forwarded-Proto", "http")
	}
	if ip := requestClientIP(src); ip != "" {
		if prior := strings.TrimSpace(src.Header.Get("X-Forwarded-For")); prior != "" {
			dst.Header.Set("X-Forwarded-For", prior+", "+ip)
		} else {
			dst.Header.Set("X-Forwarded-For", ip)
		}
	}
}

func requestClientIP(request *http.Request) string {
	host, _, err := net.SplitHostPort(strings.TrimSpace(request.RemoteAddr))
	if err == nil {
		return host
	}
	return strings.TrimSpace(request.RemoteAddr)
}

func copyHotgameProxyResponseHeaders(dst http.Header, src http.Header) {
	for key, values := range src {
		if isHopByHopHeader(key) {
			continue
		}
		for _, value := range values {
			dst.Add(key, value)
		}
	}
	if dst.Get("Content-Type") == "" {
		dst.Set("Content-Type", "application/json")
	}
}

func isHopByHopHeader(key string) bool {
	switch strings.ToLower(strings.TrimSpace(key)) {
	case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host", "content-length":
		return true
	default:
		return false
	}
}
