package userapi

import (
	"net/http"
	"strconv"
	"strings"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"hyapp/pkg/imgroup"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"

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

const maxAgencyRoomListLimit = 50

type agencyRoomsData struct {
	AgencyID   string               `json:"agency_id"`
	Rooms      []agencyRoomItemData `json:"rooms"`
	NextCursor string               `json:"next_cursor"`
}

type agencyRoomItemData 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"`
	Locked            bool   `json:"locked"`
	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"`
	CountryCode       string `json:"country_code,omitempty"`
	CountryFlag       string `json:"country_flag,omitempty"`
}

func (h *Handler) getAgencyProfile(writer http.ResponseWriter, request *http.Request) {
	targetUserID, ok := positiveInt64Query(request, "user_id")
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	agency, found, err := h.resolveAgencyForTargetHost(request, targetUserID)
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	if !found {
		httpkit.WriteOK(writer, request, map[string]any{"agency": nil})
		return
	}

	httpkit.WriteOK(writer, request, map[string]any{"agency": hostAgencyFromProto(agency)})
}

func (h *Handler) listAgencyRooms(writer http.ResponseWriter, request *http.Request) {
	targetUserID, ok := positiveInt64Query(request, "user_id")
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	limit, ok := parseAgencyRoomListLimit(request.URL.Query().Get("limit"))
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	tab, ok := parseAgencyRoomListTab(request.URL.Query().Get("tab"))
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	if h.roomQueryClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}

	agency, found, err := h.resolveAgencyForTargetHost(request, targetUserID)
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	if !found {
		httpkit.WriteOK(writer, request, agencyRoomsData{Rooms: []agencyRoomItemData{}})
		return
	}

	ownerUserIDs, err := h.activeAgencyHostUserIDs(request, agency)
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	if len(ownerUserIDs) == 0 {
		httpkit.WriteOK(writer, request, agencyRoomsData{AgencyID: int64String(agency.GetAgencyId()), Rooms: []agencyRoomItemData{}})
		return
	}

	resp, err := h.roomQueryClient.ListRoomsByOwners(request.Context(), &roomv1.ListRoomsByOwnersRequest{
		Meta:         httpkit.RoomMeta(request, "", ""),
		ViewerUserId: auth.UserIDFromContext(request.Context()),
		OwnerUserIds: ownerUserIDs,
		Tab:          tab,
		Cursor:       strings.TrimSpace(request.URL.Query().Get("cursor")),
		Limit:        limit,
		Query:        agencyRoomListQuery(request),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}

	httpkit.WriteOK(writer, request, agencyRoomsData{
		AgencyID:   int64String(agency.GetAgencyId()),
		Rooms:      agencyRoomItemsFromProto(resp.GetRooms()),
		NextCursor: resp.GetNextCursor(),
	})
}

func (h *Handler) updateAgencyProfile(writer http.ResponseWriter, request *http.Request) {
	agency, ok := h.resolveCurrentOwnerAgency(writer, request)
	if !ok {
		return
	}
	var body struct {
		CommandID    string  `json:"command_id"`
		CommandIDAlt string  `json:"commandId"`
		Name         *string `json:"name"`
		Avatar       *string `json:"avatar"`
	}
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	commandID := strings.TrimSpace(body.CommandID)
	if commandID == "" {
		commandID = strings.TrimSpace(body.CommandIDAlt)
	}
	name := trimmedOptionalString(body.Name)
	avatar := trimmedOptionalString(body.Avatar)
	if commandID == "" || (name == nil && avatar == nil) || optionalStringIsEmpty(name) || optionalStringIsEmpty(avatar) {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}

	resp, err := h.userHostClient.UpdateAgencyProfile(request.Context(), &userv1.UpdateAgencyProfileRequest{
		Meta:           httpkit.UserMeta(request, ""),
		CommandId:      commandID,
		OperatorUserId: auth.UserIDFromContext(request.Context()),
		AgencyId:       agency.GetAgencyId(),
		Name:           name,
		Avatar:         avatar,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"agency": hostAgencyFromProto(resp.GetAgency())})
}

func (h *Handler) resolveAgencyForTargetHost(request *http.Request, targetUserID int64) (*userv1.Agency, bool, error) {
	if h.userHostClient == nil {
		return nil, false, status.Error(codes.Unavailable, "user host client unavailable")
	}

	profileResp, err := h.userHostClient.GetHostProfile(request.Context(), &userv1.GetHostProfileRequest{
		Meta:   httpkit.UserMeta(request, ""),
		UserId: targetUserID,
	})
	if status.Code(err) == codes.NotFound {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, err
	}
	profile := profileResp.GetHostProfile()
	// 这两个公开查询看的是“被查询用户作为 active host 当前所属 agency”。非 host、停用 host、
	// 或没有 active agency 归属都按空结果处理，避免向客户端暴露中间态/历史关系。
	if profile == nil || profile.GetStatus() != agencyStatusActive || profile.GetCurrentAgencyId() <= 0 {
		return nil, false, nil
	}

	agencyResp, err := h.userHostClient.GetAgency(request.Context(), &userv1.GetAgencyRequest{
		Meta:     httpkit.UserMeta(request, ""),
		AgencyId: profile.GetCurrentAgencyId(),
	})
	if status.Code(err) == codes.NotFound {
		return nil, false, nil
	}
	if err != nil {
		return nil, false, err
	}
	agency := agencyResp.GetAgency()
	if agency == nil || agency.GetAgencyId() != profile.GetCurrentAgencyId() || agency.GetStatus() != agencyStatusActive {
		return nil, false, nil
	}
	return agency, true, nil
}

func (h *Handler) activeAgencyHostUserIDs(request *http.Request, agency *userv1.Agency) ([]int64, error) {
	resp, err := h.userHostClient.GetAgencyMembers(request.Context(), &userv1.GetAgencyMembersRequest{
		Meta:     httpkit.UserMeta(request, ""),
		AgencyId: agency.GetAgencyId(),
		Status:   membershipStatusActive,
	})
	if err != nil {
		return nil, err
	}

	seen := make(map[int64]struct{}, len(resp.GetMemberships())+1)
	ownerUserIDs := make([]int64, 0, len(resp.GetMemberships())+1)
	appendOwner := func(userID int64) {
		if userID <= 0 {
			return
		}
		if _, exists := seen[userID]; exists {
			return
		}
		seen[userID] = struct{}{}
		ownerUserIDs = append(ownerUserIDs, userID)
	}

	// Agency owner 自动拥有 owner membership；即使历史数据缺 owner membership，也要把 owner 的 active 房间纳入组织房间列表。
	appendOwner(agency.GetOwnerUserId())
	for _, membership := range resp.GetMemberships() {
		if membership.GetStatus() == membershipStatusActive {
			appendOwner(membership.GetHostUserId())
		}
	}
	return ownerUserIDs, nil
}

func agencyRoomItemsFromProto(rooms []*roomv1.RoomListItem) []agencyRoomItemData {
	items := make([]agencyRoomItemData, 0, len(rooms))
	for _, room := range rooms {
		items = append(items, agencyRoomItemFromProto(room))
	}
	return items
}

func agencyRoomItemFromProto(room *roomv1.RoomListItem) agencyRoomItemData {
	if room == nil {
		return agencyRoomItemData{}
	}
	roomID := room.GetRoomId()
	return agencyRoomItemData{
		RoomID:            roomID,
		IMGroupID:         agencyRoomIMGroupID(roomID),
		OwnerUserID:       int64String(room.GetOwnerUserId()),
		Title:             room.GetTitle(),
		CoverURL:          room.GetCoverUrl(),
		Mode:              room.GetMode(),
		Status:            room.GetStatus(),
		Locked:            room.GetLocked(),
		Heat:              room.GetHeat(),
		OnlineCount:       room.GetOnlineCount(),
		SeatCount:         room.GetSeatCount(),
		OccupiedSeatCount: room.GetOccupiedSeatCount(),
		VisibleRegionID:   room.GetVisibleRegionId(),
		AppCode:           room.GetAppCode(),
		RoomShortID:       room.GetRoomShortId(),
		CountryCode:       room.GetCountryCode(),
		CountryFlag:       agencyRoomCountryFlag(room.GetCountryCode()),
	}
}

func positiveInt64Query(request *http.Request, name string) (int64, bool) {
	raw := strings.TrimSpace(request.URL.Query().Get(name))
	value, err := strconv.ParseInt(raw, 10, 64)
	return value, err == nil && value > 0
}

func parseAgencyRoomListLimit(raw string) (int32, bool) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, true
	}
	value, err := strconv.ParseInt(raw, 10, 32)
	if err != nil || value <= 0 {
		return 0, false
	}
	if value > maxAgencyRoomListLimit {
		value = maxAgencyRoomListLimit
	}
	return int32(value), true
}

func parseAgencyRoomListTab(raw string) (string, bool) {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "", "hot":
		return "hot", true
	case "new":
		return "new", true
	default:
		return "", false
	}
}

func agencyRoomListQuery(request *http.Request) string {
	if query := strings.TrimSpace(request.URL.Query().Get("query")); query != "" {
		return query
	}
	return strings.TrimSpace(request.URL.Query().Get("q"))
}

func trimmedOptionalString(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	return &trimmed
}

func optionalStringIsEmpty(value *string) bool {
	return value != nil && *value == ""
}

func agencyRoomIMGroupID(roomID string) string {
	groupID, err := imgroup.RoomGroupID(roomID)
	if err != nil {
		return roomID
	}
	return groupID
}

func agencyRoomCountryFlag(country string) string {
	country = strings.ToUpper(strings.TrimSpace(country))
	if len(country) != 2 {
		return ""
	}
	runes := []rune(country)
	for _, value := range runes {
		if value < 'A' || value > 'Z' {
			return ""
		}
	}
	const regionalIndicatorA = 0x1F1E6
	return string([]rune{
		regionalIndicatorA + (runes[0] - 'A'),
		regionalIndicatorA + (runes[1] - 'A'),
	})
}
