package http

import (
	"strconv"

	"hyapp/pkg/tencentrtc"

	roomv1 "hyapp.local/api/proto/room/v1"
)

const joinRoomContributionRankLimit = 10

type roomListData struct {
	Rooms      []roomListItemData `json:"rooms"`
	NextCursor string             `json:"next_cursor,omitempty"`
}

type myRoomData struct {
	HasRoom      bool              `json:"has_room"`
	Room         *roomListItemData `json:"room,omitempty"`
	ServerTimeMS int64             `json:"server_time_ms"`
}

type roomListItemData struct {
	RoomID            string `json:"room_id"`
	IMGroupID         string `json:"im_group_id"`
	OwnerUserID       string `json:"owner_user_id,omitempty"`
	Title             string `json:"title,omitempty"`
	CoverURL          string `json:"cover_url,omitempty"`
	Mode              string `json:"mode,omitempty"`
	Status            string `json:"status,omitempty"`
	Heat              int64  `json:"heat"`
	OnlineCount       int32  `json:"online_count"`
	SeatCount         int32  `json:"seat_count"`
	OccupiedSeatCount int32  `json:"occupied_seat_count"`
	VisibleRegionID   int64  `json:"visible_region_id,omitempty"`
	AppCode           string `json:"app_code,omitempty"`
	RoomShortID       string `json:"room_short_id,omitempty"`
}

type joinRoomData struct {
	Result           roomCommandResultData `json:"result"`
	Room             roomInitialData       `json:"room"`
	Viewer           roomViewerData        `json:"viewer"`
	Seats            []roomSeatData        `json:"seats"`
	ContributionRank []roomRankItemData    `json:"contribution_rank"`
	Profiles         []roomUserProfileData `json:"profiles"`
	IM               roomIMData            `json:"im"`
	RTC              roomRTCData           `json:"rtc"`
	ServerTimeMS     int64                 `json:"server_time_ms"`
}

type roomDetailData struct {
	Room         roomInitialData          `json:"room"`
	Viewer       roomViewerData           `json:"viewer"`
	Seats        []roomSeatData           `json:"seats"`
	RankTop      []roomRankItemData       `json:"rank_top"`
	OnlineCount  int32                    `json:"online_count"`
	Permissions  roomPermissionsData      `json:"permissions"`
	Profiles     []roomDisplayProfileData `json:"profiles,omitempty"`
	IM           roomIMData               `json:"im"`
	RTC          roomRTCData              `json:"rtc"`
	ServerTimeMS int64                    `json:"server_time_ms"`
}

type roomOnlineUsersData struct {
	Items        []roomOnlineUserData `json:"items"`
	Total        int64                `json:"total"`
	Page         int32                `json:"page"`
	PageSize     int32                `json:"page_size"`
	ServerTimeMS int64                `json:"server_time_ms"`
}

type roomOnlineUserData struct {
	UserID       string                  `json:"user_id"`
	Role         string                  `json:"role,omitempty"`
	LastSeenAtMS int64                   `json:"last_seen_at_ms,omitempty"`
	Profile      *roomDisplayProfileData `json:"profile,omitempty"`
}

type roomPermissionsData struct {
	IsOwner        bool `json:"is_owner"`
	IsAdmin        bool `json:"is_admin"`
	IsMuted        bool `json:"is_muted"`
	CanSpeak       bool `json:"can_speak"`
	CanSendGift    bool `json:"can_send_gift"`
	CanMicUp       bool `json:"can_mic_up"`
	CanManageRoom  bool `json:"can_manage_room"`
	CanLeaveRoom   bool `json:"can_leave_room"`
	CanCloseRoom   bool `json:"can_close_room"`
	CanMuteMic     bool `json:"can_mute_mic"`
	CanOpenGiftBox bool `json:"can_open_gift_box"`
}

type roomDisplayProfileData struct {
	UserID        string           `json:"user_id"`
	Username      string           `json:"username"`
	Avatar        string           `json:"avatar"`
	DisplayUserID string           `json:"display_user_id"`
	VIP           map[string]any   `json:"vip"`
	Level         map[string]any   `json:"level"`
	Badges        []map[string]any `json:"badges"`
	AvatarFrame   map[string]any   `json:"avatar_frame"`
	Vehicle       map[string]any   `json:"vehicle"`
	Charm         int64            `json:"charm"`
}

type roomGiftPanelData struct {
	CoinBalance     int64                   `json:"coin_balance"`
	Recipients      []roomGiftRecipientData `json:"recipients"`
	Tabs            []roomGiftTabData       `json:"tabs"`
	Gifts           []giftConfigData        `json:"gifts"`
	QuantityPresets []int32                 `json:"quantity_presets"`
}

type roomGiftRecipientData struct {
	TargetType string                  `json:"target_type"`
	UserID     string                  `json:"user_id,omitempty"`
	SeatNo     int32                   `json:"seat_no,omitempty"`
	Label      string                  `json:"label,omitempty"`
	Profile    *roomDisplayProfileData `json:"profile,omitempty"`
}

type roomGiftTabData struct {
	Key   string `json:"key"`
	Order int32  `json:"order"`
}

type updateRoomProfileData struct {
	Result       roomCommandResultData `json:"result"`
	Room         roomInitialData       `json:"room"`
	Seats        []roomSeatData        `json:"seats"`
	ServerTimeMS int64                 `json:"server_time_ms"`
}

type roomSnapshotData struct {
	Room             roomInitialData    `json:"room"`
	Seats            []roomSeatData     `json:"seats"`
	ContributionRank []roomRankItemData `json:"contribution_rank"`
	ServerTimeMS     int64              `json:"server_time_ms"`
}

type roomCommandResultData struct {
	Applied      bool  `json:"applied"`
	RoomVersion  int64 `json:"room_version"`
	ServerTimeMS int64 `json:"server_time_ms"`
}

type roomInitialData struct {
	RoomID            string `json:"room_id"`
	IMGroupID         string `json:"im_group_id"`
	RoomShortID       string `json:"room_short_id,omitempty"`
	Title             string `json:"title,omitempty"`
	CoverURL          string `json:"cover_url,omitempty"`
	Description       string `json:"description,omitempty"`
	OwnerUserID       string `json:"owner_user_id,omitempty"`
	Mode              string `json:"mode,omitempty"`
	Status            string `json:"status,omitempty"`
	ChatEnabled       bool   `json:"chat_enabled"`
	Heat              int64  `json:"heat"`
	OnlineCount       int32  `json:"online_count"`
	SeatCount         int32  `json:"seat_count"`
	OccupiedSeatCount int32  `json:"occupied_seat_count"`
	VisibleRegionID   int64  `json:"visible_region_id,omitempty"`
	Version           int64  `json:"version"`
}

type roomViewerData struct {
	UserID       string `json:"user_id"`
	Role         string `json:"role,omitempty"`
	JoinedAtMS   int64  `json:"joined_at_ms,omitempty"`
	LastSeenAtMS int64  `json:"last_seen_at_ms,omitempty"`
}

type roomSeatData struct {
	SeatNo            int32  `json:"seat_no"`
	UserID            string `json:"user_id,omitempty"`
	Locked            bool   `json:"locked"`
	PublishState      string `json:"publish_state,omitempty"`
	MicSessionID      string `json:"mic_session_id,omitempty"`
	PublishDeadlineMS int64  `json:"publish_deadline_ms,omitempty"`
	MicMuted          bool   `json:"mic_muted"`
}

type roomRankItemData struct {
	UserID      string `json:"user_id"`
	Score       int64  `json:"score"`
	GiftValue   int64  `json:"gift_value"`
	UpdatedAtMS int64  `json:"updated_at_ms,omitempty"`
}

type roomUserProfileData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id,omitempty"`
	Username      string `json:"username,omitempty"`
	Avatar        string `json:"avatar,omitempty"`
}

type roomIMData struct {
	GroupID       string `json:"group_id"`
	NeedJoinGroup bool   `json:"need_join_group"`
}

type roomRTCData struct {
	NeedToken bool                    `json:"need_token"`
	Available bool                    `json:"available"`
	Token     *tencentrtc.TokenResult `json:"token,omitempty"`
	Reason    string                  `json:"reason,omitempty"`
}

func roomListDataFromProto(resp *roomv1.ListRoomsResponse) roomListData {
	if resp == nil {
		return roomListData{}
	}
	items := make([]roomListItemData, 0, len(resp.GetRooms()))
	for _, room := range resp.GetRooms() {
		items = append(items, roomListItemDataFromProto(room))
	}
	return roomListData{Rooms: items, NextCursor: resp.GetNextCursor()}
}

func myRoomDataFromProto(resp *roomv1.GetMyRoomResponse) myRoomData {
	if resp == nil {
		return myRoomData{}
	}
	data := myRoomData{HasRoom: resp.GetHasRoom(), ServerTimeMS: resp.GetServerTimeMs()}
	if resp.GetHasRoom() && resp.GetRoom() != nil {
		room := roomListItemDataFromProto(resp.GetRoom())
		data.Room = &room
	}
	return data
}

func roomListItemDataFromProto(room *roomv1.RoomListItem) roomListItemData {
	if room == nil {
		return roomListItemData{}
	}
	roomID := room.GetRoomId()
	return roomListItemData{
		RoomID:            roomID,
		IMGroupID:         roomIMGroupID(roomID),
		OwnerUserID:       formatOptionalUserID(room.GetOwnerUserId()),
		Title:             room.GetTitle(),
		CoverURL:          room.GetCoverUrl(),
		Mode:              room.GetMode(),
		Status:            room.GetStatus(),
		Heat:              room.GetHeat(),
		OnlineCount:       room.GetOnlineCount(),
		SeatCount:         room.GetSeatCount(),
		OccupiedSeatCount: room.GetOccupiedSeatCount(),
		VisibleRegionID:   room.GetVisibleRegionId(),
		AppCode:           room.GetAppCode(),
		RoomShortID:       room.GetRoomShortId(),
	}
}

func commandResultDataFromProto(result *roomv1.CommandResult) roomCommandResultData {
	if result == nil {
		return roomCommandResultData{}
	}
	return roomCommandResultData{
		Applied:      result.GetApplied(),
		RoomVersion:  result.GetRoomVersion(),
		ServerTimeMS: result.GetServerTimeMs(),
	}
}

func updateRoomProfileDataFromProto(resp *roomv1.UpdateRoomProfileResponse) updateRoomProfileData {
	if resp == nil {
		return updateRoomProfileData{}
	}
	snapshot := resp.GetRoom()
	return updateRoomProfileData{
		Result:       commandResultDataFromProto(resp.GetResult()),
		Room:         roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()),
		Seats:        roomSeatDataFromSnapshot(snapshot),
		ServerTimeMS: resp.GetResult().GetServerTimeMs(),
	}
}

func roomSnapshotDataFromProto(resp *roomv1.GetRoomSnapshotResponse) roomSnapshotData {
	if resp == nil {
		return roomSnapshotData{}
	}
	snapshot := resp.GetRoom()
	return roomSnapshotData{
		Room:             roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()),
		Seats:            roomSeatDataFromSnapshot(snapshot),
		ContributionRank: roomRankDataFromSnapshot(snapshot, 0),
		ServerTimeMS:     resp.GetServerTimeMs(),
	}
}

func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID string) roomInitialData {
	if snapshot == nil {
		return roomInitialData{RoomID: roomID, IMGroupID: roomIMGroupID(roomID)}
	}
	ext := snapshot.GetRoomExt()
	if roomID == "" {
		roomID = snapshot.GetRoomId()
	}
	roomShortID := snapshot.GetRoomShortId()
	if roomShortID == "" {
		roomShortID = ext["room_short_id"]
	}
	seatCount, occupiedSeatCount := roomSeatCounts(snapshot)
	return roomInitialData{
		RoomID:            roomID,
		IMGroupID:         roomIMGroupID(roomID),
		RoomShortID:       roomShortID,
		Title:             ext["title"],
		CoverURL:          ext["cover_url"],
		Description:       ext["description"],
		OwnerUserID:       formatOptionalUserID(snapshot.GetOwnerUserId()),
		Mode:              snapshot.GetMode(),
		Status:            snapshot.GetStatus(),
		ChatEnabled:       snapshot.GetChatEnabled(),
		Heat:              snapshot.GetHeat(),
		OnlineCount:       int32(len(snapshot.GetOnlineUsers())),
		SeatCount:         seatCount,
		OccupiedSeatCount: occupiedSeatCount,
		VisibleRegionID:   snapshot.GetVisibleRegionId(),
		Version:           snapshot.GetVersion(),
	}
}

func roomViewerDataFromProto(user *roomv1.RoomUser, fallbackUserID int64) roomViewerData {
	userID := fallbackUserID
	if user.GetUserId() > 0 {
		userID = user.GetUserId()
	}
	return roomViewerData{
		UserID:       formatOptionalUserID(userID),
		Role:         user.GetRole(),
		JoinedAtMS:   user.GetJoinedAtMs(),
		LastSeenAtMS: user.GetLastSeenAtMs(),
	}
}

func roomSeatDataFromSnapshot(snapshot *roomv1.RoomSnapshot) []roomSeatData {
	seats := snapshot.GetMicSeats()
	items := make([]roomSeatData, 0, len(seats))
	for _, seat := range seats {
		items = append(items, roomSeatData{
			SeatNo:            seat.GetSeatNo(),
			UserID:            formatOptionalUserID(seat.GetUserId()),
			Locked:            seat.GetLocked(),
			PublishState:      seat.GetPublishState(),
			MicSessionID:      seat.GetMicSessionId(),
			PublishDeadlineMS: seat.GetPublishDeadlineMs(),
			MicMuted:          seat.GetMicMuted(),
		})
	}
	return items
}

func roomDetailDataFromSnapshot(resp *roomv1.GetRoomSnapshotResponse, viewerUserID int64, rtc roomRTCData, profiles []roomDisplayProfileData) roomDetailData {
	if resp == nil {
		return roomDetailData{}
	}
	snapshot := resp.GetRoom()
	roomID := snapshot.GetRoomId()
	viewer := protoRoomUserByID(snapshot, viewerUserID)
	return roomDetailData{
		Room:         roomInitialRoomDataFromSnapshot(snapshot, roomID),
		Viewer:       roomViewerDataFromProto(viewer, viewerUserID),
		Seats:        roomSeatDataFromSnapshot(snapshot),
		RankTop:      roomRankDataFromSnapshot(snapshot, joinRoomContributionRankLimit),
		OnlineCount:  int32(len(snapshot.GetOnlineUsers())),
		Permissions:  roomPermissionsFromSnapshot(snapshot, viewerUserID),
		Profiles:     profiles,
		IM:           roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true},
		RTC:          rtc,
		ServerTimeMS: resp.GetServerTimeMs(),
	}
}

func roomPermissionsFromSnapshot(snapshot *roomv1.RoomSnapshot, viewerUserID int64) roomPermissionsData {
	if snapshot == nil || viewerUserID <= 0 {
		return roomPermissionsData{}
	}
	isOwner := snapshot.GetOwnerUserId() == viewerUserID
	isAdmin := containsInt64(snapshot.GetAdminUserIds(), viewerUserID)
	isMuted := containsInt64(snapshot.GetMuteUserIds(), viewerUserID)
	inRoom := protoRoomUserByID(snapshot, viewerUserID) != nil
	return roomPermissionsData{
		IsOwner:        isOwner,
		IsAdmin:        isAdmin,
		IsMuted:        isMuted,
		CanSpeak:       inRoom && snapshot.GetChatEnabled() && !isMuted,
		CanSendGift:    inRoom && snapshot.GetStatus() == "active",
		CanMicUp:       inRoom && snapshot.GetStatus() == "active" && protoSeatByUserID(snapshot, viewerUserID) == nil,
		CanManageRoom:  inRoom && (isOwner || isAdmin),
		CanLeaveRoom:   inRoom,
		CanCloseRoom:   inRoom,
		CanMuteMic:     inRoom && protoSeatByUserID(snapshot, viewerUserID) != nil,
		CanOpenGiftBox: inRoom && snapshot.GetStatus() == "active",
	}
}

func roomRankDataFromSnapshot(snapshot *roomv1.RoomSnapshot, limit int) []roomRankItemData {
	rank := snapshot.GetGiftRank()
	if limit > 0 && len(rank) > limit {
		rank = rank[:limit]
	}
	items := make([]roomRankItemData, 0, len(rank))
	for _, item := range rank {
		items = append(items, roomRankItemData{
			UserID:      formatOptionalUserID(item.GetUserId()),
			Score:       item.GetScore(),
			GiftValue:   item.GetGiftValue(),
			UpdatedAtMS: item.GetUpdatedAtMs(),
		})
	}
	return items
}

func roomInitialProfileUserIDs(snapshot *roomv1.RoomSnapshot, viewerUserID int64, rankLimit int) []int64 {
	seen := map[int64]bool{}
	userIDs := make([]int64, 0, 16)
	add := func(userID int64) {
		if userID <= 0 || seen[userID] {
			return
		}
		seen[userID] = true
		userIDs = append(userIDs, userID)
	}

	add(snapshot.GetOwnerUserId())
	add(viewerUserID)
	for _, seat := range snapshot.GetMicSeats() {
		add(seat.GetUserId())
	}
	rank := snapshot.GetGiftRank()
	if rankLimit > 0 && len(rank) > rankLimit {
		rank = rank[:rankLimit]
	}
	for _, item := range rank {
		add(item.GetUserId())
	}
	return userIDs
}

func roomOnlineUserDataFromProto(resp *roomv1.ListRoomOnlineUsersResponse, profiles map[int64]roomDisplayProfileData) roomOnlineUsersData {
	if resp == nil {
		return roomOnlineUsersData{}
	}
	items := make([]roomOnlineUserData, 0, len(resp.GetUsers()))
	for _, user := range resp.GetUsers() {
		item := roomOnlineUserData{
			UserID:       formatOptionalUserID(user.GetUserId()),
			Role:         user.GetRole(),
			LastSeenAtMS: user.GetLastSeenAtMs(),
		}
		if profile, ok := profiles[user.GetUserId()]; ok {
			copied := profile
			item.Profile = &copied
		}
		items = append(items, item)
	}
	return roomOnlineUsersData{
		Items:        items,
		Total:        resp.GetTotal(),
		Page:         resp.GetPage(),
		PageSize:     resp.GetPageSize(),
		ServerTimeMS: resp.GetServerTimeMs(),
	}
}

func protoRoomUserByID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.RoomUser {
	if snapshot == nil || userID <= 0 {
		return nil
	}
	for _, user := range snapshot.GetOnlineUsers() {
		if user.GetUserId() == userID {
			return user
		}
	}
	return nil
}

func protoSeatByUserID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatState {
	if snapshot == nil || userID <= 0 {
		return nil
	}
	for _, seat := range snapshot.GetMicSeats() {
		if seat.GetUserId() == userID {
			return seat
		}
	}
	return nil
}

func containsInt64(values []int64, target int64) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}

func roomSeatCounts(snapshot *roomv1.RoomSnapshot) (int32, int32) {
	seatCount := int32(len(snapshot.GetMicSeats()))
	occupiedSeatCount := int32(0)
	for _, seat := range snapshot.GetMicSeats() {
		if seat.GetUserId() > 0 {
			occupiedSeatCount++
		}
	}
	return seatCount, occupiedSeatCount
}

func roomIMGroupID(roomID string) string {
	// 当前房间群规则固定为腾讯云 IM GroupID 等于内部 room_id；显式下发给客户端，避免端侧硬编码规则。
	return roomID
}

func formatOptionalUserID(userID int64) string {
	if userID <= 0 {
		return ""
	}
	return strconv.FormatInt(userID, 10)
}
