package game

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	"strconv"
	"strings"
	"time"
	"unicode"

	gamev1 "hyapp.local/api/proto/game/v1"
	"hyapp/pkg/xerr"
	gamedomain "hyapp/services/game-service/internal/domain/game"
)

const (
	// AMG 文档只固定成功码 0；失败码使用稳定的 HTTP 语义数字，厂商只需按 code != 0 判失败。
	amgCodeOK                  = 0
	amgCodeInvalidRequest      = 400
	amgCodeUnauthorized        = 401
	amgCodeInsufficientBalance = 402
	amgCodeInternalError       = 500

	amgDefaultTokenTTL       = 24 * time.Hour
	amgMaxEncryptedDataBytes = 256 * 1024
)

type amgAdapterConfig struct {
	// app_key 是 AMG 分配给渠道的公开标识；RSA 私钥单独存 callbackSecret，不能混入可复制的 adapter JSON。
	AppKey          string `json:"app_key"`
	UIDMode         string `json:"uid_mode"`
	DefaultLang     string `json:"default_lang"`
	TokenTTLSeconds int64  `json:"token_ttl_seconds"`
	// coin_scale 表示一个钱包最小单位对应的 AMG 小数精度；当前金币为整数时必须配置 1。
	CoinScale int64 `json:"coin_scale"`

	// AMG 没有游戏列表接口，运营按厂商 gameId 维护每款 H5 地址，后台据此生成目录候选项。
	GameURLs          map[string]string `json:"game_urls"`
	LaunchURLTemplate string            `json:"launch_url_template"`

	// 以下参数均来自 AMG URL 文档；零值表示不拼接，避免擅自覆盖厂商游戏自己的默认行为。
	Ext      string `json:"ext"`
	Mini     int    `json:"mini"`
	AppAudio int    `json:"app_audio"`
	Debug    int    `json:"debug"`
	UseLang  int    `json:"use_lang"`
	Loading  string `json:"loading"`
}

func amgConfigFromPlatform(value any) amgAdapterConfig {
	var raw string
	switch typed := value.(type) {
	case gamedomain.Platform:
		raw = typed.AdapterConfigJSON
	case gamedomain.LaunchableGame:
		raw = typed.AdapterConfigJSON
	}
	var config amgAdapterConfig
	_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
	config.AppKey = strings.TrimSpace(config.AppKey)
	config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
	if config.UIDMode == "" {
		config.UIDMode = "display_user_id"
	}
	config.DefaultLang = strings.TrimSpace(config.DefaultLang)
	config.LaunchURLTemplate = strings.TrimSpace(config.LaunchURLTemplate)
	config.Ext = strings.TrimSpace(config.Ext)
	config.Loading = strings.TrimSpace(config.Loading)
	config.GameURLs = normalizeStringMap(config.GameURLs)
	return config
}

func (c amgAdapterConfig) TokenTTL() time.Duration {
	if c.TokenTTLSeconds > 0 {
		return time.Duration(c.TokenTTLSeconds) * time.Second
	}
	return amgDefaultTokenTTL
}

func (c amgAdapterConfig) CoinScaleValue() int64 {
	// 只接受十进制精度，防止 3、7 等比例在 JSON 数字中产生无限小数并造成钱包金额歧义。
	scale := c.CoinScale
	if scale <= 0 {
		return 1
	}
	for value := scale; value > 1; value /= 10 {
		if value%10 != 0 {
			return 1
		}
	}
	if scale > 1_000_000 {
		return 1
	}
	return scale
}

func amgLaunchBaseURL(game gamedomain.LaunchableGame, config amgAdapterConfig) string {
	for _, key := range []string{game.ProviderGameID, game.GameID, strings.ToLower(game.GameID)} {
		if raw := strings.TrimSpace(config.GameURLs[strings.TrimSpace(key)]); raw != "" {
			return raw
		}
	}
	if config.LaunchURLTemplate == "" {
		return ""
	}
	replacer := strings.NewReplacer(
		"{provider_game_id}", strings.TrimSpace(game.ProviderGameID),
		"{providerGameId}", strings.TrimSpace(game.ProviderGameID),
		"{game_id}", strings.TrimSpace(game.GameID),
		"{gameId}", strings.TrimSpace(game.GameID),
	)
	return strings.TrimSpace(replacer.Replace(config.LaunchURLTemplate))
}

func (s *Service) handleAMGOperation(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest, operation string, _ string) ([]byte, string, string, bool, string, error) {
	// RSA 私钥只能证明 settlement 密文来自持有公钥的一方；来源白名单配置后仍应先挡掉非 AMG 机房请求。
	if !callbackIPAllowed(req, platform.CallbackIPWhitelist) {
		return amgAdapterError(amgCodeUnauthorized, "ip restricted", false, "")
	}
	switch strings.ToLower(strings.Trim(strings.TrimSpace(operation), "/")) {
	case "user", "amg/user":
		return s.handleAMGUser(ctx, app, platform, req)
	case "settlement", "amg/settlement":
		return s.handleAMGSettlement(ctx, app, platform, req)
	default:
		return amgAdapterError(amgCodeInvalidRequest, "unsupported operation", false, "")
	}
}

func (s *Service) handleAMGUser(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest) ([]byte, string, string, bool, string, error) {
	token := amgAuthorizationToken(req)
	session, err := s.validAMGSession(ctx, app, token, "")
	if err != nil {
		return amgAdapterError(amgCodeUnauthorized, "token invalid", false, "")
	}
	snapshot, err := s.yomiUserSnapshot(ctx, app, req, session.UserID)
	if err != nil {
		return amgAdapterError(amgCodeFromError(err), amgMessageFromError(err), true, "")
	}
	config := amgConfigFromPlatform(platform)
	raw, contentType := amgJSON(amgCodeOK, "success", map[string]any{
		"userId":         externalUserID(session, config.UIDMode),
		"nickname":       snapshot.Nickname,
		"avatarUrl":      snapshot.Avatar,
		"availableCoins": amgWalletCoinValue(snapshot.Balance, config.CoinScaleValue()),
		"roomId":         session.RoomID,
	})
	return raw, contentType, strconv.Itoa(amgCodeOK), true, "", nil
}

type amgSettlementEnvelope struct {
	Data string `json:"data"`
}

type amgSettlementData struct {
	TransactionID string          `json:"transactionId"`
	Coins         json.Number     `json:"coins"`
	Type          int32           `json:"type"`
	Timestamp     int64           `json:"timestamp"`
	Ext           string          `json:"ext"`
	GameID        json.RawMessage `json:"gameId"`
}

func (s *Service) handleAMGSettlement(ctx context.Context, app string, platform gamedomain.Platform, req *gamev1.CallbackRequest) ([]byte, string, string, bool, string, error) {
	var envelope amgSettlementEnvelope
	if err := decodeYomiJSON(req.GetRawBody(), &envelope); err != nil || strings.TrimSpace(envelope.Data) == "" || len(envelope.Data) > amgMaxEncryptedDataBytes*2 {
		return amgAdapterError(amgCodeInvalidRequest, "invalid data", false, "")
	}
	// AMG 的 data 是 RSA PKCS#1 v1.5 分块密文再 hex 编码；callbackSecret 保存不带 PEM 头的 PKCS#8 私钥原文。
	plain, err := decryptAMGData(envelope.Data, platform.CallbackSecretCiphertext)
	if err != nil {
		return amgAdapterError(amgCodeUnauthorized, "data decrypt failed", false, "")
	}
	var body amgSettlementData
	if err := decodeYomiJSON(plain, &body); err != nil {
		return amgAdapterError(amgCodeInvalidRequest, "invalid settlement data", true, "")
	}
	body.TransactionID = strings.TrimSpace(body.TransactionID)
	body.Ext = strings.TrimSpace(body.Ext)
	gameID := reyouJSONText(body.GameID)
	gameIDValue, gameIDOK := amgPositiveInt64(gameID)
	config := amgConfigFromPlatform(platform)
	amount, amountOK := amgWalletCoinAmount(body.Coins, config.CoinScaleValue())
	opType := yomiOpType(body.Type)
	if body.TransactionID == "" || len(body.TransactionID) > 64 || !gameIDOK || gameIDValue <= 0 || !amountOK || amount < 0 || opType == "" || body.Timestamp <= 0 {
		return amgAdapterError(amgCodeInvalidRequest, "invalid settlement data", true, body.TransactionID)
	}
	token := amgAuthorizationToken(req)
	session, err := s.validAMGSession(ctx, app, token, gameID)
	if err != nil {
		return amgAdapterError(amgCodeUnauthorized, "token invalid", true, body.TransactionID)
	}
	// RSA PKCS#1 v1.5 每次加密都会产生不同密文；幂等摘要必须基于解密后的业务字段，不能使用原始 callback body。
	requestHash := stableHash(strings.Join([]string{
		body.TransactionID,
		strconv.FormatInt(amount, 10),
		strconv.FormatInt(int64(body.Type), 10),
		strconv.FormatInt(body.Timestamp, 10),
		body.Ext,
		gameID,
	}, "|"))
	result, err := s.applyYomiCoinChange(ctx, app, req, session, body.TransactionID, "", opType, amount, "", requestHash)
	if err != nil {
		code := amgCodeFromError(err)
		balance := s.bestEffortCoinBalance(ctx, app, req.GetMeta().GetRequestId(), session.UserID)
		raw, contentType := amgJSON(code, amgMessageFromError(err), map[string]any{
			"availableCoins": amgWalletCoinValue(balance, config.CoinScaleValue()),
		})
		return raw, contentType, strconv.Itoa(code), true, body.TransactionID, nil
	}
	// 重复 transactionId 直接复用历史余额并返回成功；applyYomiCoinChange 已保证不会再次调用钱包。
	raw, contentType := amgJSON(amgCodeOK, "success", map[string]any{
		"availableCoins": amgWalletCoinValue(result.BalanceAfter, config.CoinScaleValue()),
	})
	return raw, contentType, strconv.Itoa(amgCodeOK), true, body.TransactionID, nil
}

func (s *Service) validAMGSession(ctx context.Context, app string, token string, providerGameID string) (gamedomain.LaunchSession, error) {
	token = strings.TrimSpace(token)
	if token == "" {
		return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid")
	}
	// 同一 App access token 可连续打开多个游戏；平台和可选 gameId 一起查询最新 session，避免串到其它厂商或旧房间。
	session, err := s.repository.GetLaunchSessionByTokenScope(ctx, app, token, gamedomain.PlatformCodeAMG, strings.TrimSpace(providerGameID))
	if err != nil {
		return gamedomain.LaunchSession{}, err
	}
	if session.Status != gamedomain.SessionActive || session.ExpiresAtMS <= s.now().UnixMilli() {
		return gamedomain.LaunchSession{}, xerr.New(xerr.SessionExpired, "token expired")
	}
	return session, nil
}

func amgAuthorizationToken(req *gamev1.CallbackRequest) string {
	if req == nil {
		return ""
	}
	for _, key := range []string{"Authorization", "authorization"} {
		value := strings.TrimSpace(req.GetHeaders()[key])
		if len(value) >= len("Bearer ") && strings.EqualFold(value[:len("Bearer ")], "Bearer ") {
			value = strings.TrimSpace(value[len("Bearer "):])
		}
		if value != "" {
			return value
		}
	}
	return ""
}

func decryptAMGData(data string, privateKeyValue string) ([]byte, error) {
	data = strings.TrimSpace(data)
	if data == "" || len(data) > amgMaxEncryptedDataBytes*2 {
		return nil, fmt.Errorf("AMG ciphertext is empty or too large")
	}
	ciphertext, err := hex.DecodeString(data)
	if err != nil || len(ciphertext) == 0 {
		return nil, fmt.Errorf("decode AMG ciphertext: %w", err)
	}
	privateKey, err := parseAMGPrivateKey(privateKeyValue)
	if err != nil {
		return nil, err
	}
	chunkSize := privateKey.PublicKey.Size()
	if len(ciphertext)%chunkSize != 0 {
		return nil, fmt.Errorf("AMG ciphertext chunk size is invalid")
	}
	plain := make([]byte, 0, len(ciphertext))
	for start := 0; start < len(ciphertext); start += chunkSize {
		chunk, decryptErr := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext[start:start+chunkSize])
		if decryptErr != nil {
			return nil, fmt.Errorf("decrypt AMG ciphertext: %w", decryptErr)
		}
		plain = append(plain, chunk...)
		if len(plain) > amgMaxEncryptedDataBytes {
			return nil, fmt.Errorf("AMG plaintext is too large")
		}
	}
	return plain, nil
}

func parseAMGPrivateKey(value string) (*rsa.PrivateKey, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return nil, fmt.Errorf("AMG private key is empty")
	}
	var der []byte
	if block, _ := pem.Decode([]byte(value)); block != nil {
		der = block.Bytes
	} else {
		// 厂商交付文件是单行 base64 PKCS#8；容忍复制时混入的换行，但不修改实际密钥字节。
		compact := strings.Map(func(r rune) rune {
			if unicode.IsSpace(r) {
				return -1
			}
			return r
		}, value)
		decoded, err := base64.StdEncoding.DecodeString(compact)
		if err != nil {
			return nil, fmt.Errorf("decode AMG private key: %w", err)
		}
		der = decoded
	}
	if parsed, err := x509.ParsePKCS8PrivateKey(der); err == nil {
		privateKey, ok := parsed.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("AMG private key is not RSA")
		}
		if err := privateKey.Validate(); err != nil {
			return nil, fmt.Errorf("validate AMG private key: %w", err)
		}
		return privateKey, nil
	}
	// 兼容厂商后续交付传统 PKCS#1 文件；当前 private(1).txt 已验证为 PKCS#8。
	privateKey, err := x509.ParsePKCS1PrivateKey(der)
	if err != nil {
		return nil, fmt.Errorf("parse AMG private key: %w", err)
	}
	if err := privateKey.Validate(); err != nil {
		return nil, fmt.Errorf("validate AMG private key: %w", err)
	}
	return privateKey, nil
}

func amgWalletCoinAmount(value json.Number, scale int64) (int64, bool) {
	text := strings.TrimSpace(value.String())
	if text == "" {
		return 0, false
	}
	rat, ok := new(big.Rat).SetString(text)
	if !ok {
		return 0, false
	}
	rat.Mul(rat, new(big.Rat).SetInt64(scale))
	if !rat.IsInt() || !rat.Num().IsInt64() {
		return 0, false
	}
	return rat.Num().Int64(), true
}

func amgWalletCoinValue(value int64, scale int64) json.Number {
	if scale <= 1 {
		return json.Number(strconv.FormatInt(value, 10))
	}
	digits := len(strconv.FormatInt(scale, 10)) - 1
	negative := value < 0
	abs := value
	if abs < 0 {
		abs = -abs
	}
	text := fmt.Sprintf("%d.%0*d", abs/scale, digits, abs%scale)
	text = strings.TrimRight(strings.TrimRight(text, "0"), ".")
	if negative {
		text = "-" + text
	}
	return json.Number(text)
}

func amgPositiveInt64(value string) (int64, bool) {
	parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
	return parsed, err == nil && parsed > 0
}

func amgCodeFromError(err error) int {
	switch {
	case err == nil:
		return amgCodeOK
	case xerr.IsCode(err, xerr.InsufficientBalance):
		return amgCodeInsufficientBalance
	case xerr.IsCode(err, xerr.Unauthorized), xerr.IsCode(err, xerr.SessionExpired):
		return amgCodeUnauthorized
	case xerr.IsCode(err, xerr.InvalidArgument), xerr.IsCode(err, xerr.IdempotencyConflict):
		return amgCodeInvalidRequest
	default:
		return amgCodeInternalError
	}
}

func amgMessageFromError(err error) string {
	switch amgCodeFromError(err) {
	case amgCodeInsufficientBalance:
		return "insufficient balance"
	case amgCodeUnauthorized:
		return "token invalid"
	case amgCodeInvalidRequest:
		return "invalid request"
	default:
		return "system error"
	}
}

func amgAdapterError(code int, message string, authenticated bool, providerRequestID string) ([]byte, string, string, bool, string, error) {
	raw, contentType := amgJSON(code, message, map[string]any{})
	return raw, contentType, strconv.Itoa(code), authenticated, providerRequestID, nil
}

func amgJSON(code int, message string, data any) ([]byte, string) {
	if strings.TrimSpace(message) == "" {
		message = "success"
	}
	if data == nil {
		data = map[string]any{}
	}
	return jsonResponse(map[string]any{"code": code, "data": data, "msg": message})
}
