package luckygift

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

	"hyapp-luck-gateway/internal/integration/luckygiftclient"
	"hyapp-luck-gateway/internal/requestid"
	"hyapp-luck-gateway/internal/response"
)

type Handler struct {
	client         luckygiftclient.Client
	allowedApps    map[string]struct{}
	requestTimeout time.Duration
}

func New(client luckygiftclient.Client, requestTimeout time.Duration, allowedApps map[string]struct{}) *Handler {
	if requestTimeout <= 0 {
		requestTimeout = 3 * time.Second
	}
	return &Handler{client: client, allowedApps: cloneAllowedApps(allowedApps), requestTimeout: requestTimeout}
}

type sendRequest struct {
	AppCode        string `json:"app_code"`
	RequestID      string `json:"request_id"`
	ExternalUserID string `json:"external_user_id"`
	// DeviceID 由外部 App 在自己的认证/请求签名边界内绑定。
	// luck-gateway 目前只做 App allowlist，不能独立证明该值来自真实设备；dynamic_v3 由 owner 对缺失值 fail-close。
	DeviceID string `json:"device_id"`
	// PaidAtMS 是外部 App 自己完成扣费的 UTC epoch ms；调用方必须把它纳入自己的认证/签名边界。
	// gateway 不用到达时间替代。fixed_v2 允许 0 兼容，dynamic_v3 由 owner 读取规则后强制要求正值。
	PaidAtMS int64 `json:"paid_at_ms"`
	// UserRegisteredAtMS 是外部 App 用户主数据中的账号创建时间，不是本次请求首次出现时间。
	// fixed_v2 允许 0 兼容旧接入；dynamic_v3 缺失时由 owner 让补偿大奖资格 fail-close。
	UserRegisteredAtMS int64          `json:"user_registered_at_ms"`
	GiftCount          int64          `json:"gift_count"`
	UnitAmount         int64          `json:"unit_amount"`
	Currency           string         `json:"currency"`
	Metadata           map[string]any `json:"metadata"`
}

func (h *Handler) Send(w http.ResponseWriter, r *http.Request) {
	requestID := requestid.FromContext(r.Context())
	var req sendRequest
	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&req); err != nil {
		response.Fail(w, http.StatusBadRequest, response.CodeBadRequest, requestID, "invalid lucky gift send body")
		return
	}
	if err := req.validate(); err != nil {
		response.Fail(w, http.StatusBadRequest, response.CodeBadRequest, requestID, err.Error())
		return
	}
	appCode := normalizeAppCode(req.AppCode)
	if !h.isAllowedApp(appCode) {
		response.Fail(w, http.StatusForbidden, response.CodeForbidden, requestID, "app_code is not allowed")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), h.requestTimeout)
	defer cancel()
	result, err := h.client.SendLuckyGift(ctx, luckygiftclient.SendCommand{
		TraceRequestID:     requestID,
		AppCode:            appCode,
		RequestID:          strings.TrimSpace(req.RequestID),
		ExternalUserID:     strings.TrimSpace(req.ExternalUserID),
		DeviceID:           strings.TrimSpace(req.DeviceID),
		UserRegisteredAtMS: req.UserRegisteredAtMS,
		GiftCount:          req.GiftCount,
		UnitAmount:         req.UnitAmount,
		TotalAmount:        req.GiftCount * req.UnitAmount,
		Currency:           strings.TrimSpace(req.Currency),
		MetadataJSON:       metadataJSON(req.Metadata),
		// pool 仍由 owner 默认规则选择；paid_at_ms 则必须保持外部账务事实，不能改写成 gateway 收包时间。
		PaidAtMS: req.PaidAtMS,
	})
	if err != nil {
		response.Fail(w, http.StatusInternalServerError, response.CodeServerError, requestID, "send lucky gift failed")
		return
	}
	response.OK(w, requestID, result)
}

func (r sendRequest) validate() error {
	if normalizeAppCode(r.AppCode) == "" {
		return errors.New("app_code is required")
	}
	if len(normalizeAppCode(r.AppCode)) > 32 {
		return errors.New("app_code is too long")
	}
	if strings.TrimSpace(r.RequestID) == "" {
		return errors.New("request_id is required")
	}
	if len(strings.TrimSpace(r.RequestID)) > 128 {
		return errors.New("request_id is too long")
	}
	if strings.TrimSpace(r.ExternalUserID) == "" {
		return errors.New("external_user_id is required")
	}
	if len(strings.TrimSpace(r.ExternalUserID)) > 128 {
		return errors.New("external_user_id is too long")
	}
	if len(strings.TrimSpace(r.DeviceID)) > 128 {
		return errors.New("device_id is too long")
	}
	if r.PaidAtMS < 0 {
		return errors.New("paid_at_ms is invalid")
	}
	if r.UserRegisteredAtMS < 0 {
		return errors.New("user_registered_at_ms is invalid")
	}
	// 不在这里统一要求 device_id/user_registered_at_ms：网关无法获知本次命中的不可变规则是 fixed_v2 还是 dynamic_v3。
	// owner 读到规则后对 dynamic_v3 权威处理缺失事实，fixed_v2 继续兼容旧调用方。
	if r.GiftCount <= 0 || r.UnitAmount <= 0 {
		return errors.New("gift_count and unit_amount must be positive")
	}
	if r.GiftCount > 999 {
		return errors.New("gift_count cannot exceed 999")
	}
	// 网关只接受数量和单价；total_amount 由服务端计算，避免外部 App SDK 或恶意调用方传入不一致账面金额。
	if r.GiftCount > (1<<63-1)/r.UnitAmount {
		return errors.New("gift amount is too large")
	}
	if currency := strings.ToUpper(strings.TrimSpace(r.Currency)); currency != "" && currency != "COIN" {
		// 当前 RTP、奖池和风控都以金币等价单位推进；未配置汇率前拒绝其他币种，避免污染奖池水位。
		return errors.New("currency must be COIN")
	}
	if len(strings.TrimSpace(r.Currency)) > 16 {
		return errors.New("currency is too long")
	}
	return nil
}

func metadataJSON(value map[string]any) string {
	if len(value) == 0 {
		return "{}"
	}
	body, err := json.Marshal(value)
	if err != nil {
		// JSON decoder 已保证 map 可序列化；失败时返回空对象，让 owner service 不因展示元数据阻断抽奖。
		return "{}"
	}
	return string(body)
}

func (h *Handler) isAllowedApp(appCode string) bool {
	if len(h.allowedApps) == 0 {
		return false
	}
	_, ok := h.allowedApps[normalizeAppCode(appCode)]
	return ok
}

func cloneAllowedApps(input map[string]struct{}) map[string]struct{} {
	out := make(map[string]struct{}, len(input))
	for app := range input {
		if normalized := normalizeAppCode(app); normalized != "" {
			out[normalized] = struct{}{}
		}
	}
	return out
}

func normalizeAppCode(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}
