package http

import (
	"context"
	"io"
	"net/http"
	"strings"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/imgroup"
	"hyapp/pkg/tencentim"
	"hyapp/pkg/userleaderboard"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/client"
	"hyapp/services/gateway-service/internal/financewithdrawal"
	"hyapp/services/gateway-service/internal/transport/http/appapi"
	"hyapp/services/gateway-service/internal/transport/http/giftlimit"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"

	userv1 "hyapp.local/api/proto/user/v1"
)

// Handler 是 gateway 的 HTTP transport adapter。
// 它只依赖跨服务 client，不持有房间状态，也不直接实现房间命令语义。
type Handler struct {
	roomClient           client.RoomClient
	roomGuardClient      client.RoomGuardClient
	roomQueryClient      client.RoomQueryClient
	userClient           client.UserAuthClient
	userIdentityClient   client.UserIdentityClient
	userProfileClient    client.UserProfileClient
	userInviteClient     client.UserInviteClient
	userSocialClient     client.UserSocialClient
	userCPClient         client.UserCPClient
	userDeviceClient     client.UserDeviceClient
	userCountryClient    client.UserCountryQueryClient
	userRegionClient     client.UserRegionClient
	userHostClient       client.UserHostClient
	userHostAdminClient  client.UserHostAdminClient
	appRegistryClient    client.AppRegistryClient
	walletClient         client.WalletClient
	messageClient        client.MessageInboxClient
	messageActionClient  client.MessageActionConfirmClient
	taskClient           client.TaskClient
	growthLevelClient    client.GrowthLevelClient
	achievementClient    client.AchievementClient
	registrationReward   client.RegistrationRewardClient
	firstRechargeReward  client.FirstRechargeRewardClient
	cumulativeRecharge   client.CumulativeRechargeRewardClient
	inviteActivityReward client.InviteActivityRewardClient
	sevenDayCheckIn      client.SevenDayCheckInClient
	luckyGift            client.LuckyGiftClient
	roomTurnoverReward   client.RoomTurnoverRewardClient
	weeklyStar           client.WeeklyStarClient
	cpWeeklyRank         client.CPWeeklyRankClient
	agencyOpening        client.AgencyOpeningClient
	wheel                client.WheelClient
	broadcastClient      client.BroadcastClient
	gameClient           client.GameClient
	statisticsClient     client.StatisticsClient
	appConfigReader      appapi.ConfigReader
	withdrawalWriter     financewithdrawal.Writer
	withdrawalNotifier   financewithdrawal.Notifier
	userLeaderboard      *userleaderboard.Store
	tencentIM            TencentIMConfig
	tencentIMImporter    TencentIMAccountImporter
	tencentIMPublisher   TencentIMUserMessagePublisher
	tencentRTC           TencentRTCConfig
	objectUploader       ObjectUploader
	authRateLimitConfig  AuthRateLimitConfig
	authRateLimiter      publicAuthRateLimiter
	giftCapacityEnabled  bool
	giftCapacityLimiter  giftlimit.Limiter
	giftRequestTimeout   time.Duration
	giftMaxTargets       int
	giftMaxCount         int
	giftMaxUnits         int
	loginRiskConfig      LoginRiskConfig
	loginRiskCache       loginRiskCache

	activityTemplateRuntime client.ActivityTemplateRuntimeClient
}

// TencentIMConfig 是 gateway transport 层签发客户端腾讯云 IM 登录票据所需的配置。
type TencentIMConfig struct {
	SDKAppID          int64
	SecretKey         string
	UserSigTTL        time.Duration
	AdminIdentifier   string
	CallbackAuthToken string
	GroupIDPrefix     string
}

// TencentIMAccountImporter 是 gateway 在签发 UserSig 前补偿导入账号的最小边界。
type TencentIMAccountImporter interface {
	ImportAccount(ctx context.Context, userID int64, nickname string, faceURL string) error
}

// TencentIMUserMessagePublisher 是 gateway 服务端代发 C2C custom message 的最小边界。
type TencentIMUserMessagePublisher interface {
	PublishUserCustomMessage(ctx context.Context, message tencentim.CustomUserMessage) error
}

// TencentRTCConfig 是 gateway transport 层签发客户端腾讯 RTC 进房票据所需的配置。
type TencentRTCConfig struct {
	Enabled         bool
	SDKAppID        int64
	SecretKey       string
	UserSigTTL      time.Duration
	RoomIDType      string
	AppScene        string
	CallbackSignKey string
}

// ObjectUploader 是 gateway 上传接口依赖的对象存储最小能力。
type ObjectUploader interface {
	PutObject(ctx context.Context, key string, reader io.Reader, sizeBytes int64, contentType string) (string, error)
}

// NewHandler 装配 gateway HTTP handler 的内部依赖。
func NewHandler(roomClient client.RoomClient, userClient ...client.UserAuthClient) *Handler {
	config := defaultAuthRateLimitConfig()
	handler := &Handler{roomClient: roomClient, authRateLimitConfig: config, authRateLimiter: newMemoryAuthRateLimiter(config), loginRiskConfig: defaultLoginRiskConfig()}
	if len(userClient) > 0 {
		handler.userClient = userClient[0]
	}

	return handler
}

// NewHandlerWithClients 装配需要用户资料接口的 gateway HTTP handler。
func NewHandlerWithClients(roomClient client.RoomClient, userClient client.UserAuthClient, userIdentityClient client.UserIdentityClient, userProfileClient ...client.UserProfileClient) *Handler {
	handler := &Handler{
		roomClient:         roomClient,
		userClient:         userClient,
		userIdentityClient: userIdentityClient,
		loginRiskConfig:    defaultLoginRiskConfig(),
	}
	handler.SetAuthRateLimit(defaultAuthRateLimitConfig())
	if len(userProfileClient) > 0 {
		handler.userProfileClient = userProfileClient[0]
	}

	return handler
}

// NewHandlerWithConfig 装配完整 gateway HTTP handler 和外部云服务配置。
func NewHandlerWithConfig(roomClient client.RoomClient, roomGuardClient client.RoomGuardClient, userClient client.UserAuthClient, userIdentityClient client.UserIdentityClient, tencentIM TencentIMConfig, userProfileClient ...client.UserProfileClient) *Handler {
	handler := &Handler{
		roomClient:         roomClient,
		roomGuardClient:    roomGuardClient,
		userClient:         userClient,
		userIdentityClient: userIdentityClient,
		tencentIM:          tencentIM,
		loginRiskConfig:    defaultLoginRiskConfig(),
	}
	handler.SetAuthRateLimit(defaultAuthRateLimitConfig())
	if len(userProfileClient) > 0 {
		handler.userProfileClient = userProfileClient[0]
	}

	return handler
}

// SetTencentRTC 注入 RTC 签发配置；缺失或 disabled 时 token endpoint 会 fail-closed。
func (h *Handler) SetTencentRTC(config TencentRTCConfig) {
	h.tencentRTC = config
}

// SetTencentIMAccountImporter 注入腾讯 IM 账号导入边界；nil 表示仅签发 UserSig，不做 REST 补偿。
func (h *Handler) SetTencentIMAccountImporter(importer TencentIMAccountImporter) {
	h.tencentIMImporter = importer
}

// SetTencentIMUserMessagePublisher 注入服务端 C2C 投递边界；nil 时依赖该能力的接口必须 fail-closed。
func (h *Handler) SetTencentIMUserMessagePublisher(publisher TencentIMUserMessagePublisher) {
	h.tencentIMPublisher = publisher
}

// SetObjectUploader 注入对象存储上传实现；生产环境使用腾讯云 COS。
func (h *Handler) SetObjectUploader(uploader ObjectUploader) {
	h.objectUploader = uploader
}

// SetGiftCapacityProtection 注入跨 gateway 节点共享的送礼容量保护和 HTTP 编排边界。
// enabled=true 且 limiter=nil 会在请求路径 fail-closed，避免配置漂移时静默失去保护。
func (h *Handler) SetGiftCapacityProtection(enabled bool, requestTimeout time.Duration, maxTargets int, limiter giftlimit.Limiter) {
	h.giftCapacityEnabled = enabled
	h.giftCapacityLimiter = limiter
	h.giftRequestTimeout = requestTimeout
	h.giftMaxTargets = maxTargets
	if h.giftMaxCount <= 0 {
		h.giftMaxCount = 999
	}
	if h.giftMaxUnits <= 0 {
		h.giftMaxUnits = 5_000
	}
}

// SetGiftRequestLimits keeps protocol hard limits separate from Redis availability. Even when the
// distributed limiter is intentionally disabled, malformed or explosive gift_count*target_count
// requests still fail before profile, wallet and Room Cell work begins.
func (h *Handler) SetGiftRequestLimits(maxCount int, maxUnits int) {
	if maxCount > 0 {
		h.giftMaxCount = maxCount
	}
	if maxUnits > 0 {
		h.giftMaxUnits = maxUnits
	}
}

// SetRoomQueryClient 注入 room-service 读模型查询 client。
func (h *Handler) SetRoomQueryClient(roomQueryClient client.RoomQueryClient) {
	h.roomQueryClient = roomQueryClient
}

// SetUserCountryQueryClient 注入注册页国家列表查询 client。
func (h *Handler) SetUserCountryQueryClient(userCountryClient client.UserCountryQueryClient) {
	h.userCountryClient = userCountryClient
}

// SetUserRegionClient 注入 user-service 区域国家归属查询 client。
func (h *Handler) SetUserRegionClient(userRegionClient client.UserRegionClient) {
	h.userRegionClient = userRegionClient
}

// SetUserDeviceClient 注入 user-service 设备推送 token client。
func (h *Handler) SetUserDeviceClient(userDeviceClient client.UserDeviceClient) {
	h.userDeviceClient = userDeviceClient
}

// SetUserInviteClient 注入 H5 邀请页搜索和绑定邀请人 client。
func (h *Handler) SetUserInviteClient(userInviteClient client.UserInviteClient) {
	h.userInviteClient = userInviteClient
}

// SetUserHostClient 注入 user-service host domain 身份查询 client。
func (h *Handler) SetUserHostClient(userHostClient client.UserHostClient) {
	h.userHostClient = userHostClient
}

// SetUserHostAdminClient 注入 user-service host admin domain 写入口 client。
func (h *Handler) SetUserHostAdminClient(userHostAdminClient client.UserHostAdminClient) {
	h.userHostAdminClient = userHostAdminClient
}

// SetUserSocialClient 注入 user-service 访问、关注和好友关系 client。
func (h *Handler) SetUserSocialClient(userSocialClient client.UserSocialClient) {
	h.userSocialClient = userSocialClient
}

// SetUserCPClient 注入 user-service CP/兄弟/姐妹关系 client。
func (h *Handler) SetUserCPClient(userCPClient client.UserCPClient) {
	h.userCPClient = userCPClient
}

// SetAppRegistryClient 注入 App 注册表解析 client；gateway 用它把包名转换成内部 app_code。
func (h *Handler) SetAppRegistryClient(appRegistryClient client.AppRegistryClient) {
	h.appRegistryClient = appRegistryClient
}

// SetWalletClient 注入 wallet-service 余额查询 client。
func (h *Handler) SetWalletClient(walletClient client.WalletClient) {
	h.walletClient = walletClient
}

// SetMessageInboxClient 注入 activity-service message inbox client。
func (h *Handler) SetMessageInboxClient(messageClient client.MessageInboxClient) {
	h.messageClient = messageClient
}

// SetMessageActionConfirmClient 注入 activity-service action-confirm client。
func (h *Handler) SetMessageActionConfirmClient(messageActionClient client.MessageActionConfirmClient) {
	h.messageActionClient = messageActionClient
}

// SetTaskClient 注入 activity-service task client。
func (h *Handler) SetTaskClient(taskClient client.TaskClient) {
	h.taskClient = taskClient
}

// SetGrowthLevelClient 注入 activity-service 等级查询 client。
func (h *Handler) SetGrowthLevelClient(growthLevelClient client.GrowthLevelClient) {
	h.growthLevelClient = growthLevelClient
}

// SetAchievementClient 注入 activity-service 成就和徽章 client。
func (h *Handler) SetAchievementClient(achievementClient client.AchievementClient) {
	h.achievementClient = achievementClient
}

// SetRegistrationRewardClient 注入 activity-service 注册奖励查询 client。
func (h *Handler) SetRegistrationRewardClient(registrationReward client.RegistrationRewardClient) {
	h.registrationReward = registrationReward
}

// SetFirstRechargeRewardClient 注入 activity-service 首冲奖励查询 client。
func (h *Handler) SetFirstRechargeRewardClient(firstRechargeReward client.FirstRechargeRewardClient) {
	h.firstRechargeReward = firstRechargeReward
}

// SetCumulativeRechargeRewardClient 注入 activity-service 累充奖励查询 client。
func (h *Handler) SetCumulativeRechargeRewardClient(cumulativeRecharge client.CumulativeRechargeRewardClient) {
	h.cumulativeRecharge = cumulativeRecharge
}

// SetInviteActivityRewardClient 注入 activity-service 邀请活动查询和领取 client。
func (h *Handler) SetInviteActivityRewardClient(inviteActivityReward client.InviteActivityRewardClient) {
	h.inviteActivityReward = inviteActivityReward
}

// SetSevenDayCheckInClient 注入 activity-service 七日签到 client。
func (h *Handler) SetSevenDayCheckInClient(sevenDayCheckIn client.SevenDayCheckInClient) {
	h.sevenDayCheckIn = sevenDayCheckIn
}

// SetLuckyGiftClient 注入 lucky-gift-service 资格检查 client；HTTP 层只保留送礼前查询入口，
// 具体规则版本、奖池和体验池判断都留在独立 owner service 内。
func (h *Handler) SetLuckyGiftClient(luckyGift client.LuckyGiftClient) {
	h.luckyGift = luckyGift
}

// SetRoomTurnoverRewardClient 注入 activity-service 房间流水奖励查询 client。
func (h *Handler) SetRoomTurnoverRewardClient(roomTurnoverReward client.RoomTurnoverRewardClient) {
	h.roomTurnoverReward = roomTurnoverReward
}

// SetWeeklyStarClient 注入 activity-service 周星活动 client。
func (h *Handler) SetWeeklyStarClient(weeklyStar client.WeeklyStarClient) {
	h.weeklyStar = weeklyStar
}

// SetCPWeeklyRankClient 注入 activity-service CP 周榜活动 client。
func (h *Handler) SetCPWeeklyRankClient(cpWeeklyRank client.CPWeeklyRankClient) {
	h.cpWeeklyRank = cpWeeklyRank
}

// SetAgencyOpeningClient 注入 activity-service 代理开业活动 client。
func (h *Handler) SetAgencyOpeningClient(agencyOpening client.AgencyOpeningClient) {
	h.agencyOpening = agencyOpening
}

// SetWheelClient 注入 activity-service 转盘配置和抽奖 client。
func (h *Handler) SetWheelClient(wheel client.WheelClient) {
	h.wheel = wheel
}

// SetActivityTemplateRuntimeClient 注入已发布活动模版的 App 运行态入口。
// gateway 只增加鉴权和资料展示编排，不在 HTTP 层复制版本、任务、榜单或发奖状态机。
func (h *Handler) SetActivityTemplateRuntimeClient(runtime client.ActivityTemplateRuntimeClient) {
	h.activityTemplateRuntime = runtime
}

// SetBroadcastClient 注入 activity-service 播报群成员关系 client。
func (h *Handler) SetBroadcastClient(broadcastClient client.BroadcastClient) {
	h.broadcastClient = broadcastClient
}

// SetGameClient 注入 game-service 游戏列表、启动和回调 client。
func (h *Handler) SetGameClient(gameClient client.GameClient) {
	h.gameClient = gameClient
}

// SetStatisticsClient 注入 statistics-service 内部 HTTP client；gateway 只转发 H5 埋点，不在入口层做统计聚合。
func (h *Handler) SetStatisticsClient(statisticsClient client.StatisticsClient) {
	h.statisticsClient = statisticsClient
}

// SetUserLeaderboardStore 注入 App 用户榜 Redis 读模型；gateway 只读 activity-service 的聚合结果。
func (h *Handler) SetUserLeaderboardStore(store *userleaderboard.Store) {
	h.userLeaderboard = store
}

// SetAppConfigReader 注入后台 App 配置读取依赖。
func (h *Handler) SetAppConfigReader(reader appapi.ConfigReader) {
	h.appConfigReader = reader
}

// SetWithdrawalApplicationWriter 注入后台 finance 提现申请写入边界。
// gateway 创建申请前会先通过 wallet-service 冻结工资，写表失败时必须释放冻结余额。
func (h *Handler) SetWithdrawalApplicationWriter(writer financewithdrawal.Writer) {
	h.withdrawalWriter = writer
}

// SetWithdrawalApplicationNotifier 注入财务提醒边界；通知失败只记录日志，不能回滚已冻结和已落库申请。
func (h *Handler) SetWithdrawalApplicationNotifier(notifier financewithdrawal.Notifier) {
	h.withdrawalNotifier = notifier
}

func normalizeCommandID(commandID string) string {
	// command_id 是客户端按用户动作生成的写命令幂等键；gateway 只裁剪空白，不自动补值。
	return strings.TrimSpace(commandID)
}

// decode 统一处理 HTTP JSON 入参解析失败分支。
// gateway 在这里拒绝非法 JSON，不做房间业务校验，避免把 Room Cell 规则复制到入口层。
func decode(writer http.ResponseWriter, request *http.Request, out any) bool {
	if err := httpkit.DecodeJSON(request.Body, out); err != nil {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body")
		return false
	}

	return true
}

// write 统一把内部 gRPC 调用结果转换为 HTTP JSON 响应。
// 首版不在 gateway 细分 gRPC 错误码，避免入口层提前耦合 room-service 的业务错误模型。
func write(writer http.ResponseWriter, request *http.Request, body any, err error) {
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}

	httpkit.WriteOK(writer, request, body)
}

func (h *Handler) issueTencentIMUserSig(writer http.ResponseWriter, request *http.Request) {
	userID := auth.UserIDFromContext(request.Context())
	if userID <= 0 {
		// 正常路径已由 auth middleware 拦截；这里兜底避免签发匿名 IM 票据。
		httpkit.WriteError(writer, request, http.StatusUnauthorized, httpkit.CodeUnauthorized, "unauthorized")
		return
	}
	if h.userProfileClient == nil {
		// 播报群 join 列表必须来自 user-service 当前资料，不能靠 token 快照猜区域。
		httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error")
		return
	}

	userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
		Meta:   authRequestMeta(request, ""),
		UserId: userID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	user := userResp.GetUser()
	if user == nil {
		httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error")
		return
	}
	if !user.GetProfileCompleted() {
		httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodeProfileRequired, "profile required")
		return
	}
	app := appcode.FromContext(request.Context())
	if user.GetAppCode() != "" && appcode.Normalize(user.GetAppCode()) != app {
		// UserSig 是 IM 登录凭证，不能给跨 app_code 用户签发，否则会绕过播报群租户隔离。
		httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
		return
	}

	result, err := tencentim.GenerateUserSig(tencentim.UserSigConfig{
		SDKAppID:  h.tencentIM.SDKAppID,
		SecretKey: h.tencentIM.SecretKey,
		TTL:       h.tencentIM.UserSigTTL,
	}, tencentim.FormatUserID(userID), time.Now().UTC())
	if err != nil {
		if h.tencentIMImporter != nil {
			_ = h.tencentIMImporter.ImportAccount(request.Context(), userID, user.GetUsername(), user.GetAvatar())
			result, err = tencentim.GenerateUserSig(tencentim.UserSigConfig{
				SDKAppID:  h.tencentIM.SDKAppID,
				SecretKey: h.tencentIM.SecretKey,
				TTL:       h.tencentIM.UserSigTTL,
			}, tencentim.FormatUserID(userID), time.Now().UTC())
		}
	}
	if err != nil {
		// 缺少 SDKAppID 或 SecretKey 属于服务端配置错误，不能返回假票据给客户端。
		httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error")
		return
	}
	if h.tencentIMImporter != nil {
		if err := h.tencentIMImporter.ImportAccount(request.Context(), userID, user.GetUsername(), user.GetAvatar()); err != nil {
			// UserSig 只证明签名有效；账号未导入时 SDK C2C 仍会失败，因此这里必须 fail-closed。
			httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error")
			return
		}
	}

	joinGroups, ok := imJoinGroupsForUser(app, user.GetRegionId(), h.tencentIM.GroupIDPrefix)
	if !ok {
		// 生成 join_groups 失败代表服务端 app_code/region_id 配置异常，不能返回缺失群列表让客户端自行猜。
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}

	httpkit.WriteOK(writer, request, tencentIMUserSigResponse{
		SDKAppID:   result.SDKAppID,
		UserID:     result.UserID,
		UserSig:    result.UserSig,
		ExpireAtMS: result.ExpireAtMS,
		JoinGroups: joinGroups,
	})
}

type tencentIMUserSigResponse struct {
	SDKAppID   int64                `json:"sdk_app_id"`
	UserID     string               `json:"user_id"`
	UserSig    string               `json:"user_sig"`
	ExpireAtMS int64                `json:"expire_at_ms"`
	JoinGroups []tencentIMJoinGroup `json:"join_groups"`
}

type tencentIMJoinGroup struct {
	GroupID  string `json:"group_id"`
	Type     string `json:"type"`
	RegionID int64  `json:"region_id,omitempty"`
}

func imJoinGroupsForUser(app string, regionID int64, groupIDPrefix ...string) ([]tencentIMJoinGroup, bool) {
	prefix := ""
	if len(groupIDPrefix) > 0 {
		prefix = groupIDPrefix[0]
	}
	globalGroupID, err := imgroup.GlobalBroadcastGroupIDWithPrefix(prefix, app)
	if err != nil {
		return nil, false
	}
	// 全局群始终返回；实际消息量应很低，主要用于全服级运营通知或极少数全局事件。
	groups := []tencentIMJoinGroup{{
		GroupID: globalGroupID,
		Type:    string(imgroup.KindGlobalBroadcast),
	}}
	if regionID > 0 {
		// 区域群来自 user-service 当前 region_id，客户端不能自选区域群；区域变更后下一次 UserSig 返回新群。
		regionGroupID, err := imgroup.RegionBroadcastGroupIDWithPrefix(prefix, app, regionID)
		if err != nil {
			return nil, false
		}
		groups = append(groups, tencentIMJoinGroup{
			GroupID:  regionGroupID,
			Type:     string(imgroup.KindRegionBroadcast),
			RegionID: regionID,
		})
	}
	return groups, true
}
