package httpkit

import (
	"context"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"net/netip"
	"strconv"
	"strings"
	"time"

	"google.golang.org/grpc/status"
	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/pkg/xerr"
	"hyapp/services/gateway-service/internal/auth"

	activityv1 "hyapp.local/api/proto/activity/v1"
	luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
	roomv1 "hyapp.local/api/proto/room/v1"
	userv1 "hyapp.local/api/proto/user/v1"
)

type requestIDContextKey struct{}

const (
	CodeOK                 = "OK"
	CodeInvalidJSON        = "INVALID_JSON"
	CodeInvalidArgument    = "INVALID_ARGUMENT"
	CodeUnauthorized       = "UNAUTHORIZED"
	CodeAccessTokenExpired = "ACCESS_TOKEN_EXPIRED"
	CodePermissionDenied   = "PERMISSION_DENIED"
	CodeProfileRequired    = "PROFILE_REQUIRED"
	CodeAuthLoginBlocked   = "AUTH_LOGIN_BLOCKED"
	CodeNotFound           = "NOT_FOUND"
	CodeRateLimited        = "RATE_LIMITED"
	CodeUpstreamError      = "UPSTREAM_ERROR"
	CodeInternalError      = "INTERNAL_ERROR"
)

// ResponseEnvelope 是 gateway 暴露给客户端的稳定 JSON 外壳。
// HTTP status 表示传输层结果，code 表示客户端可依赖的错误语义。
type ResponseEnvelope struct {
	Code      string `json:"code"`
	Message   string `json:"message"`
	RequestID string `json:"request_id"`
	Data      any    `json:"data,omitempty"`
}

// WithRequestID 为每个业务 API 请求建立服务端追踪 ID。
// 入站 X-Request-ID 不作为可信输入，避免客户端把追踪字段误用成跨请求幂等锚点。
func WithRequestID(next http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		requestID := idgen.New("req")

		writer.Header().Set("X-Request-ID", requestID)
		ctx := context.WithValue(request.Context(), requestIDContextKey{}, requestID)
		next.ServeHTTP(writer, request.WithContext(ctx))
	})
}

// RequestIDFromContext 读取当前 HTTP 请求的追踪 ID。
// 理论上业务路由都会先经过 WithRequestID；兜底生成值只用于防御错误调用。
func RequestIDFromContext(ctx context.Context) string {
	requestID, _ := ctx.Value(requestIDContextKey{}).(string)
	if requestID != "" {
		return requestID
	}

	return idgen.New("req")
}

// DecodeJSON 隔离 JSON 解码细节，避免 handler 直接依赖 envelope 写法。
func DecodeJSON(reader io.Reader, out any) error {
	return json.NewDecoder(reader).Decode(out)
}

// Decode 统一处理 HTTP JSON 入参解析失败分支。
// gateway 在这里拒绝非法 JSON，不把脏请求继续透传到内部 gRPC。
func Decode(writer http.ResponseWriter, request *http.Request, out any) bool {
	if err := DecodeJSON(request.Body, out); err != nil {
		WriteError(writer, request, http.StatusBadRequest, CodeInvalidJSON, "invalid request body")
		return false
	}

	return true
}

// WriteOK 写成功 envelope。
func WriteOK(writer http.ResponseWriter, request *http.Request, data any) {
	WriteEnvelope(writer, http.StatusOK, ResponseEnvelope{
		Code:      CodeOK,
		Message:   "ok",
		RequestID: ResponseRequestID(request),
		Data:      data,
	})
}

// WriteError 写失败 envelope。
// 错误 message 保持稳定、短小，排障依赖 request_id 追日志，而不是把内部错误直接暴露给客户端。
func WriteError(writer http.ResponseWriter, request *http.Request, statusCode int, code string, message string) {
	WriteErrorData(writer, request, statusCode, code, message, nil)
}

// WriteErrorData 写入客户端可执行的结构化错误详情。
// data 只能来自业务错误的显式安全 metadata 或 handler 已判定的公开状态，不能放原始 err。
func WriteErrorData(writer http.ResponseWriter, request *http.Request, statusCode int, code string, message string, data any) {
	WriteEnvelope(writer, statusCode, ResponseEnvelope{
		Code:      code,
		Message:   message,
		RequestID: ResponseRequestID(request),
		Data:      data,
	})
}

// WriteRPCError 把内部 gRPC status + ErrorInfo.reason 转成外部稳定 envelope。
func WriteRPCError(writer http.ResponseWriter, request *http.Request, err error) {
	if err == nil {
		return
	}
	st, ok := status.FromError(err)
	if !ok {
		WriteError(writer, request, http.StatusBadGateway, CodeUpstreamError, "upstream service error")
		return
	}

	reason := xerr.ReasonFromGRPC(err)
	statusCode, code, message := MapReasonToHTTP(reason)
	if reason == xerr.DeviceAlreadyRegistered {
		// 设备注册上限错误需要把 user-service 组装的账号数量和短号列表透给客户端。
		if detail := strings.TrimSpace(st.Message()); detail != "" {
			message = detail
		}
	}
	WriteErrorData(writer, request, statusCode, code, message, rpcErrorData(err))
}

// rpcErrorData 将 ErrorInfo.metadata 转成 HTTP JSON data，并只对已登记的等级字段做数值化。
// google.rpc 的 metadata wire 类型固定为 string；这里显式 ParseInt 后，Flutter 才能按整数
// 比较 required_level/current_effective_level，其余字段继续保持稳定字符串语义。
func rpcErrorData(err error) map[string]any {
	metadata := xerr.MetadataFromGRPC(err)
	if len(metadata) == 0 {
		return nil
	}
	data := make(map[string]any, len(metadata))
	for key, value := range metadata {
		switch key {
		case "required_level", "current_effective_level":
			level, parseErr := strconv.ParseInt(value, 10, 32)
			if parseErr == nil {
				data[key] = level
				continue
			}
		}
		data[key] = value
	}
	return data
}

// Write 统一把内部 gRPC 调用结果转换为 HTTP JSON 响应。
func Write(writer http.ResponseWriter, request *http.Request, body any, err error) {
	if err != nil {
		WriteRPCError(writer, request, err)
		return
	}

	WriteOK(writer, request, body)
}

func MapReasonToHTTP(reason xerr.Code) (int, string, string) {
	spec := xerr.SpecOf(reason)
	return spec.HTTPStatus, spec.PublicCode, spec.PublicMessage
}

// WriteEnvelope 是 gateway 业务 API 的唯一 JSON 响应出口。
func WriteEnvelope(writer http.ResponseWriter, statusCode int, response ResponseEnvelope) {
	writer.Header().Set("Content-Type", "application/json")
	writer.WriteHeader(statusCode)
	if err := json.NewEncoder(writer).Encode(response); err != nil {
		return
	}
}

// ResponseRequestID 统一确定响应中的 request_id。
func ResponseRequestID(request *http.Request) string {
	return RequestIDFromContext(request.Context())
}

// PositiveInt64PathValue 只接受 ServeMux 已命中的单段正整数路径变量。
// 非法路径参数在 transport 层返回 4xx，避免把坏 ID 继续透传到内部 gRPC。
func PositiveInt64PathValue(request *http.Request, name string) (int64, bool) {
	raw := strings.TrimSpace(request.PathValue(name))
	value, err := strconv.ParseInt(raw, 10, 64)
	return value, err == nil && value > 0
}

func UserIDString(userID int64) string {
	if userID > 0 {
		return strconv.FormatInt(userID, 10)
	}
	return ""
}

func PositiveInt32Query(request *http.Request, name string, fallback int32) (int32, bool) {
	raw := strings.TrimSpace(request.URL.Query().Get(name))
	if raw == "" {
		return fallback, true
	}
	value, err := strconv.ParseInt(raw, 10, 32)
	if err != nil || value <= 0 {
		return 0, false
	}
	return int32(value), true
}

func FirstQueryOrHeader(request *http.Request, queryKey string, headerNames ...string) string {
	if value := strings.TrimSpace(request.URL.Query().Get(queryKey)); value != "" {
		return value
	}
	return FirstHeader(request, headerNames...)
}

func FirstHeader(request *http.Request, headerNames ...string) string {
	for _, name := range headerNames {
		if value := strings.TrimSpace(request.Header.Get(name)); value != "" {
			return value
		}
	}
	return ""
}

// RequireMethod 在业务 handler 之前固定 HTTP method，避免 path-only mux 让错误 method 进入命令链路。
func RequireMethod(method string, next http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		if request.Method != method {
			WriteError(writer, request, http.StatusMethodNotAllowed, CodeInvalidArgument, "invalid argument")
			return
		}

		next(writer, request)
	}
}

// RequireCompletedProfile 在 gateway 层执行外部准入门禁。
// 这里故意不访问 user-service，依赖 access token 快照，避免所有高频房间入口同步打用户服务。
func RequireCompletedProfile(next http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		if !auth.ProfileCompletedFromContext(request.Context()) {
			WriteError(writer, request, http.StatusForbidden, CodeProfileRequired, "profile required")
			return
		}

		next.ServeHTTP(writer, request)
	}
}

// RoomMeta 把外部 HTTP 请求上下文转换成 room-service 命令元信息。
// request_id 只做链路追踪；command_id 必须来自客户端用户动作，gateway 只裁剪空白。
func RoomMeta(request *http.Request, roomID string, commandID string) *roomv1.RequestMeta {
	sessionID := auth.SessionIDFromContext(request.Context())
	if sessionID == "" {
		sessionID = idgen.New("sess")
	}
	return &roomv1.RequestMeta{
		RequestId:     RequestIDFromContext(request.Context()),
		CommandId:     strings.TrimSpace(commandID),
		ActorUserId:   auth.UserIDFromContext(request.Context()),
		RoomId:        roomID,
		AppCode:       appcode.FromContext(request.Context()),
		GatewayNodeId: "gateway-local",
		SessionId:     sessionID,
		// 设备作用域只从已验签 JWT 读取；旧 JWT 返回空值，不用临时 session_id 兜底。
		DeviceId: auth.DeviceIDFromContext(request.Context()),
		SentAtMs: time.Now().UnixMilli(),
	}
}

// UserMeta 生成 user-service RPC 的统一追踪和审计元信息。
func UserMeta(request *http.Request, deviceID string) *userv1.RequestMeta {
	return UserMetaWithLoginContext(request, deviceID, "", "", "")
}

func UserMetaWithLoginContext(request *http.Request, deviceID string, platform string, language string, timezone string) *userv1.RequestMeta {
	return &userv1.RequestMeta{
		RequestId:     RequestIDFromContext(request.Context()),
		Caller:        "gateway-service",
		GatewayNodeId: "gateway-local",
		SentAtMs:      time.Now().UnixMilli(),
		DeviceId:      deviceID,
		ClientIp:      ClientIP(request),
		UserAgent:     request.UserAgent(),
		CountryByIp:   CountryByIP(request),
		SessionId:     auth.SessionIDFromContext(request.Context()),
		AppCode:       appcode.FromContext(request.Context()),
		Platform:      strings.ToLower(strings.TrimSpace(platform)),
		Language:      strings.TrimSpace(language),
		Timezone:      strings.TrimSpace(timezone),
		// 设备资料头由 App 全局注入到每个请求；user-service 只在认证成功后把它们聚合进 user_devices。
		DeviceModel:        FirstHeader(request, "X-Device-Model"),
		DeviceManufacturer: FirstHeader(request, "X-Device-Manufacturer"),
		OsVersion:          FirstHeader(request, "X-OS-Version"),
		Imei:               FirstHeader(request, "X-Device-IMEI"),
	}
}

// ActivityMeta 生成 activity-service RPC 的统一追踪元信息。
// 业务 actor 仍由各请求显式传 user_id，meta 只承载链路和租户上下文。
func ActivityMeta(request *http.Request) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		RequestId:     RequestIDFromContext(request.Context()),
		Caller:        "gateway-service",
		GatewayNodeId: "gateway-local",
		SentAtMs:      time.Now().UnixMilli(),
		AppCode:       appcode.FromContext(request.Context()),
	}
}

// LuckyGiftMeta 生成 lucky-gift-service RPC 的追踪元信息。
// request_id 仍只用于链路排查，送礼和外部抽奖的幂等键必须由各自业务命令字段承载。
func LuckyGiftMeta(request *http.Request) *luckygiftv1.RequestMeta {
	return &luckygiftv1.RequestMeta{
		RequestId:     RequestIDFromContext(request.Context()),
		Caller:        "gateway-service",
		GatewayNodeId: "gateway-local",
		SentAtMs:      time.Now().UnixMilli(),
		AppCode:       appcode.FromContext(request.Context()),
	}
}

// ClientIP 提取 gateway 看到的客户端地址；X-Real-IP 在线上可能是入口代理地址，所以只在 XFF 没有公网段时兜底使用。
func ClientIP(request *http.Request) string {
	if ip := NormalizeIP(request.Header.Get("X-Trusted-Client-IP")); ip != "" {
		return ip
	}
	if ip := FirstPublicForwardedIP(request.Header.Get("X-Forwarded-For")); ip != "" {
		return ip
	}
	if ip := PublicHeaderIP(request.Header.Get("X-Real-IP")); ip != "" {
		return ip
	}

	host, _, err := net.SplitHostPort(request.RemoteAddr)
	if err != nil {
		return NormalizeIP(request.RemoteAddr)
	}

	return host
}

func FirstPublicForwardedIP(forwarded string) string {
	forwarded = strings.TrimSpace(forwarded)
	if forwarded == "" {
		return ""
	}
	for part := range strings.SplitSeq(forwarded, ",") {
		ip := NormalizeIP(part)
		if ip == "" {
			continue
		}
		if addr, err := netip.ParseAddr(ip); err == nil && isPublicClientAddr(addr) {
			return ip
		}
	}
	return ""
}

func PublicHeaderIP(value string) string {
	ip := NormalizeIP(value)
	if ip == "" {
		return ""
	}
	if addr, err := netip.ParseAddr(ip); err == nil && isPublicClientAddr(addr) {
		return ip
	}
	return ""
}

func NormalizeIP(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return ""
	}
	if host, _, err := net.SplitHostPort(value); err == nil {
		value = host
	}
	if addr, err := netip.ParseAddr(value); err == nil {
		return addr.String()
	}
	return value
}

func isPublicClientAddr(addr netip.Addr) bool {
	return addr.IsValid() &&
		addr.IsGlobalUnicast() &&
		!addr.IsPrivate() &&
		!addr.IsLoopback() &&
		!addr.IsLinkLocalUnicast() &&
		!addr.IsLinkLocalMulticast() &&
		!addr.IsMulticast() &&
		!addr.IsUnspecified()
}

func CountryByIP(request *http.Request) string {
	// country_by_ip 不接受 JSON body；只采信 gateway/边缘层写入的地域头。
	for _, header := range []string{"CF-IPCountry", "CloudFront-Viewer-Country", "X-Vercel-IP-Country", "X-AppEngine-Country", "X-Country-Code"} {
		country := strings.ToUpper(strings.TrimSpace(request.Header.Get(header)))
		if country == "" || country == "XX" || country == "UNKNOWN" {
			continue
		}
		if len(country) >= 2 && len(country) <= 3 {
			return country
		}
	}

	return ""
}
