package httpkit

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

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

type requestIDContextKey struct{}

const (
	CodeOK               = "OK"
	CodeInvalidJSON      = "INVALID_JSON"
	CodeInvalidArgument  = "INVALID_ARGUMENT"
	CodeUnauthorized     = "UNAUTHORIZED"
	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)
}

// 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) {
	WriteEnvelope(writer, statusCode, ResponseEnvelope{
		Code:      code,
		Message:   message,
		RequestID: ResponseRequestID(request),
	})
}

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

	reason := xerr.ReasonFromGRPC(err)
	statusCode, code, message := MapReasonToHTTP(reason)
	WriteError(writer, request, statusCode, code, message)
}

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
}

// 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)
	}
}
