package appapi

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/apptracking"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/client"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type appEventsRequest struct {
	Events []appEventRequest `json:"events"`
}

type appEventRequest struct {
	EventID         string          `json:"event_id"`
	EventName       string          `json:"event_name"`
	EventType       string          `json:"event_type"`
	Screen          string          `json:"screen"`
	TargetType      string          `json:"target_type"`
	TargetID        string          `json:"target_id"`
	DeviceID        string          `json:"device_id"`
	SessionID       string          `json:"session_id"`
	ClientSessionID string          `json:"client_session_id"`
	Platform        string          `json:"platform"`
	AppVersion      string          `json:"app_version"`
	Language        string          `json:"language"`
	Timezone        string          `json:"timezone"`
	DurationMS      int64           `json:"duration_ms"`
	Success         bool            `json:"success"`
	ErrorCode       string          `json:"error_code"`
	Properties      json.RawMessage `json:"properties"`
	OccurredAtMS    int64           `json:"occurred_at_ms"`
}

func (h *Handler) reportAppEvents(writer http.ResponseWriter, request *http.Request) {
	var body appEventsRequest
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	if !apptracking.ValidBatchSize(len(body.Events)) {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}

	nowMS := time.Now().UTC().UnixMilli()
	userID := auth.UserIDFromContext(request.Context())
	headerDeviceID := httpkit.FirstHeader(request, "X-Device-ID", "X-HY-Device-ID")
	headerPlatform := httpkit.FirstHeader(request, "X-App-Platform", "X-Platform")
	headerAppVersion := httpkit.FirstHeader(request, "X-App-Version", "X-Client-Version")
	headerLanguage := appEventRequestLanguage(request)
	headerTimezone := httpkit.FirstHeader(request, "X-Timezone", "X-App-Timezone")

	events := make([]client.AppTrackingEvent, 0, len(body.Events))
	for _, item := range body.Events {
		event, valid := appTrackingEventFromRequest(item, appTrackingEventDefaults{
			appCode:      appcode.FromContext(request.Context()),
			userID:       userID,
			deviceID:     headerDeviceID,
			sessionID:    auth.SessionIDFromContext(request.Context()),
			platform:     headerPlatform,
			appVersion:   headerAppVersion,
			language:     headerLanguage,
			timezone:     headerTimezone,
			receivedAtMS: nowMS,
		})
		if !valid {
			httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
			return
		}
		events = append(events, event)
	}
	if h.statisticsClient == nil {
		// App 埋点事实由 statistics-service 持久化；gateway 未注入写入边界时不能假成功，否则客户端会丢掉重试机会。
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	// 先完成客户端可控字段的全部长度校验，再读取 user-service。这样超限
	// 请求稳定返回 400，不会制造无意义的下游调用，更不会被包装成 502。
	countryID, regionID, ok := h.appEventUserDimensions(writer, request, userID)
	if !ok {
		return
	}
	for index := range events {
		events[index].CountryID = countryID
		events[index].RegionID = regionID
	}

	result, err := h.statisticsClient.ReportAppTrackingEvents(request.Context(), events)
	if err != nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	httpkit.WriteOK(writer, request, result)
}

func (h *Handler) appEventUserDimensions(writer http.ResponseWriter, request *http.Request, userID int64) (int64, int64, bool) {
	if userID <= 0 {
		return 0, 0, true
	}
	if h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return 0, 0, false
	}
	resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
		Meta:   httpkit.UserMeta(request, httpkit.FirstHeader(request, "X-Device-ID", "X-HY-Device-ID")),
		UserId: userID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return 0, 0, false
	}
	user := resp.GetUser()
	if user == nil {
		// 登录态缺用户主数据属于服务端依赖异常；不能退化成匿名埋点，否则会污染用户维度。
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return 0, 0, false
	}
	return user.GetCountryId(), user.GetRegionId(), true
}

type appTrackingEventDefaults struct {
	appCode      string
	userID       int64
	deviceID     string
	sessionID    string
	platform     string
	appVersion   string
	language     string
	timezone     string
	receivedAtMS int64
}

func appTrackingEventFromRequest(item appEventRequest, defaults appTrackingEventDefaults) (client.AppTrackingEvent, bool) {
	deviceID := firstTrimmed(item.DeviceID, defaults.deviceID)
	properties, ok := normalizeAppEventProperties(item.Properties)
	if !ok {
		return client.AppTrackingEvent{}, false
	}
	occurredAtMS := item.OccurredAtMS
	if occurredAtMS <= 0 {
		occurredAtMS = defaults.receivedAtMS
	}
	event := client.AppTrackingEvent{
		AppCode:         defaults.appCode,
		EventID:         item.EventID,
		EventName:       item.EventName,
		EventType:       item.EventType,
		Screen:          item.Screen,
		TargetType:      item.TargetType,
		TargetID:        item.TargetID,
		UserID:          defaults.userID,
		DeviceID:        deviceID,
		SessionID:       firstTrimmed(defaults.sessionID, item.SessionID),
		ClientSessionID: item.ClientSessionID,
		AuthSessionID:   defaults.sessionID,
		Platform:        strings.ToLower(firstTrimmed(item.Platform, defaults.platform)),
		AppVersion:      firstTrimmed(item.AppVersion, defaults.appVersion),
		Language:        firstTrimmed(item.Language, defaults.language),
		Timezone:        firstTrimmed(item.Timezone, defaults.timezone),
		DurationMS:      item.DurationMS,
		Success:         item.Success,
		ErrorCode:       item.ErrorCode,
		Properties:      properties,
		OccurredAtMS:    occurredAtMS,
	}
	if !normalizeAppTrackingEventText(&event) || event.EventID == "" || event.EventName == "" {
		return client.AppTrackingEvent{}, false
	}
	if defaults.userID <= 0 && event.DeviceID == "" {
		// 匿名埋点没有服务端 user_id，只能依赖稳定 device_id 做排障和后续粗粒度去重。
		return client.AppTrackingEvent{}, false
	}
	return event, true
}

func normalizeAppEventProperties(raw json.RawMessage) (json.RawMessage, bool) {
	return apptracking.NormalizeProperties(raw)
}

func normalizeAppTrackingEventText(event *client.AppTrackingEvent) bool {
	if event == nil {
		return false
	}
	// Every field uses the same byte limit as statistics persistence. Returning
	// false is deliberate: identifiers and diagnostic error codes must be
	// accepted intact or rejected, never silently truncated into another fact.
	fields := []struct {
		value    *string
		maxBytes int
	}{
		{value: &event.AppCode, maxBytes: apptracking.MaxAppCodeBytes},
		{value: &event.EventID, maxBytes: apptracking.MaxEventIDBytes},
		{value: &event.EventName, maxBytes: apptracking.MaxEventNameBytes},
		{value: &event.EventType, maxBytes: apptracking.MaxEventTypeBytes},
		{value: &event.Screen, maxBytes: apptracking.MaxScreenBytes},
		{value: &event.TargetType, maxBytes: apptracking.MaxTargetTypeBytes},
		{value: &event.TargetID, maxBytes: apptracking.MaxTargetIDBytes},
		{value: &event.DeviceID, maxBytes: apptracking.MaxDeviceIDBytes},
		{value: &event.SessionID, maxBytes: apptracking.MaxSessionIDBytes},
		{value: &event.ClientSessionID, maxBytes: apptracking.MaxSessionIDBytes},
		{value: &event.AuthSessionID, maxBytes: apptracking.MaxSessionIDBytes},
		{value: &event.Platform, maxBytes: apptracking.MaxPlatformBytes},
		{value: &event.AppVersion, maxBytes: apptracking.MaxAppVersionBytes},
		{value: &event.Language, maxBytes: apptracking.MaxLanguageBytes},
		{value: &event.Timezone, maxBytes: apptracking.MaxTimezoneBytes},
		{value: &event.ErrorCode, maxBytes: apptracking.MaxErrorCodeBytes},
	}
	for _, field := range fields {
		normalized, ok := apptracking.NormalizeText(*field.value, field.maxBytes)
		if !ok {
			return false
		}
		*field.value = normalized
	}
	return true
}

func appEventRequestLanguage(request *http.Request) string {
	value := httpkit.FirstHeader(request, "X-App-Language", "X-Language")
	if value == "" {
		value = request.Header.Get("Accept-Language")
	}
	if index := strings.Index(value, ","); index >= 0 {
		value = value[:index]
	}
	return strings.TrimSpace(value)
}

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