package userapi

import (
	"net/http"
	"strings"

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

const (
	profileVisitorPageMax     int32 = 100
	profileVisitorPageSizeMax int32 = 100
)

type profileVisitRecordData struct {
	VisitorUserID         string `json:"visitor_user_id,omitempty"`
	AnonymousVisitID      string `json:"anonymous_visit_id,omitempty"`
	VisitorIdentityHidden bool   `json:"visitor_identity_hidden"`
	TargetUserID          string `json:"target_user_id"`
	VisitCount            int64  `json:"visit_count"`
	LastVisitedAtMS       int64  `json:"last_visited_at_ms"`
}

type followRecordData struct {
	FollowerUserID string `json:"follower_user_id"`
	FolloweeUserID string `json:"followee_user_id"`
	FollowedAtMS   int64  `json:"followed_at_ms"`
}

type friendRecordData struct {
	UserID       string `json:"user_id"`
	FriendUserID string `json:"friend_user_id"`
	FriendedAtMS int64  `json:"friended_at_ms"`
}

type friendApplicationData struct {
	RequesterUserID string `json:"requester_user_id"`
	TargetUserID    string `json:"target_user_id"`
	Status          string `json:"status"`
	CreatedAtMS     int64  `json:"created_at_ms"`
	UpdatedAtMS     int64  `json:"updated_at_ms"`
}

// listMyProfileVisitors 返回访问我的主页的人；这是关系 read model，不实时扫用户行为日志。
func (h *Handler) listMyProfileVisitors(writer http.ResponseWriter, request *http.Request) {
	if h.userSocialClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	if page > profileVisitorPageMax {
		// 当前 page 协议需要从公开、匿名两个有序分支读取到目标 offset 后再合并；限制页码可把
		// 单次读取窗口硬限制在 10,000 条，避免客户端构造高页把 user-service 内存变成无界缓存。
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "page must be less than or equal to 100")
		return
	}
	if pageSize > profileVisitorPageSizeMax {
		// 访客列表的页宽是对外契约；网关必须在进入 gRPC 前拒绝超限请求，
		// 不依赖 user-service 静默截断，否则 Flutter 无法区分参数错误与服务端分页。
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "page_size must be less than or equal to 100")
		return
	}
	resp, err := h.userSocialClient.ListProfileVisitors(request.Context(), &userv1.ListProfileVisitorsRequest{
		Meta:     httpkit.UserMeta(request, ""),
		UserId:   auth.UserIDFromContext(request.Context()),
		Page:     page,
		PageSize: pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	records := make([]profileVisitRecordData, 0, len(resp.GetRecords()))
	for _, record := range resp.GetRecords() {
		records = append(records, profileVisitData(record))
	}
	httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}

// listMyFollowing 返回我关注的用户列表；关注我的列表后续单独建接口，避免语义混淆。
func (h *Handler) listMyFollowing(writer http.ResponseWriter, request *http.Request) {
	if h.userSocialClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.userSocialClient.ListFollowing(request.Context(), &userv1.ListFollowingRequest{
		Meta:     httpkit.UserMeta(request, ""),
		UserId:   auth.UserIDFromContext(request.Context()),
		Page:     page,
		PageSize: pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	records := make([]followRecordData, 0, len(resp.GetRecords()))
	for _, record := range resp.GetRecords() {
		records = append(records, followData(record))
	}
	httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}

// listMyFriends 返回双向好友列表。
func (h *Handler) listMyFriends(writer http.ResponseWriter, request *http.Request) {
	if h.userSocialClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.userSocialClient.ListFriends(request.Context(), &userv1.ListFriendsRequest{
		Meta:     httpkit.UserMeta(request, ""),
		UserId:   auth.UserIDFromContext(request.Context()),
		Page:     page,
		PageSize: pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	records := make([]friendRecordData, 0, len(resp.GetRecords()))
	for _, record := range resp.GetRecords() {
		records = append(records, friendData(record))
	}
	httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}

// listMyFriendApplications 返回待处理申请；direction=incoming/outgoing。
func (h *Handler) listMyFriendApplications(writer http.ResponseWriter, request *http.Request) {
	if h.userSocialClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, pageSize, ok := socialPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.userSocialClient.ListFriendApplications(request.Context(), &userv1.ListFriendApplicationsRequest{
		Meta:      httpkit.UserMeta(request, ""),
		UserId:    auth.UserIDFromContext(request.Context()),
		Direction: strings.TrimSpace(request.URL.Query().Get("direction")),
		Page:      page,
		PageSize:  pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	applications := make([]friendApplicationData, 0, len(resp.GetApplications()))
	for _, application := range resp.GetApplications() {
		applications = append(applications, friendApplicationDataFromProto(application))
	}
	httpkit.WriteOK(writer, request, map[string]any{"applications": applications, "total": resp.GetTotal()})
}

// userSocialAction 处理 /users/{user_id}/visit|follow|friend-requests|friend-requests/accept|friend。
func (h *Handler) userSocialAction(writer http.ResponseWriter, request *http.Request) {
	if h.userSocialClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	targetUserID, ok := httpkit.PositiveInt64PathValue(request, "user_id")
	if !ok {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	action := strings.Trim(request.PathValue("action"), "/")

	switch action {
	case "visit":
		h.recordProfileVisit(writer, request, targetUserID)
	case "follow":
		h.handleFollowAction(writer, request, targetUserID)
	case "friend-requests":
		h.applyFriend(writer, request, targetUserID)
	case "friend-requests/accept":
		h.acceptFriendApplication(writer, request, targetUserID)
	case "friend":
		h.deleteFriend(writer, request, targetUserID)
	default:
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
	}
}

func (h *Handler) recordProfileVisit(writer http.ResponseWriter, request *http.Request, targetUserID int64) {
	if request.Method != http.MethodPost {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	resp, err := h.userSocialClient.RecordProfileVisit(request.Context(), &userv1.RecordProfileVisitRequest{
		Meta:          httpkit.UserMeta(request, ""),
		VisitorUserId: auth.UserIDFromContext(request.Context()),
		TargetUserId:  targetUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	data := map[string]any{
		"recorded":            resp.GetRecorded(),
		"target_stats_hidden": resp.GetTargetStatsHidden(),
	}
	if !resp.GetTargetStatsHidden() {
		// 隐藏标志为 true 时不写 target_stats key；不能把 nil 转成零值 DTO，否则客户端会把伪造的 0 当成真实统计。
		data["target_stats"] = targetProfileStatsFromProto(resp.GetTargetStats())
	}
	if h.userProfileClient != nil {
		profileResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
			Meta:   httpkit.UserMeta(request, ""),
			UserId: targetUserID,
		})
		if err == nil {
			// 访问记录归 social read model，资料卡展示归 user-service 用户主资料；这里仅拼装 App 打开别人资料卡所需的当前快照。
			data["target_profile"] = profileData(profileResp.GetUser(), 0)
		}
	}
	httpkit.WriteOK(writer, request, data)
}

// targetProfileStatsFromProto 为客态资料卡返回粉丝计数。已发布的 Fami 客户端曾把
// target_stats.friends_count 当作 Followers 读取，因此在新 followers_count 合同外保留该客态回包别名；
// /users/me/overview 仍返回真实好友数，不会污染好友列表语义。
func targetProfileStatsFromProto(stats *userv1.UserProfileStats) overviewProfileStats {
	result := overviewStatsFromProto(stats)
	result.FriendsCount = result.FollowersCount
	return result
}

func (h *Handler) handleFollowAction(writer http.ResponseWriter, request *http.Request, followeeUserID int64) {
	userID := auth.UserIDFromContext(request.Context())
	switch request.Method {
	case http.MethodPost:
		resp, err := h.userSocialClient.FollowUser(request.Context(), &userv1.FollowUserRequest{
			Meta:           httpkit.UserMeta(request, ""),
			FollowerUserId: userID,
			FolloweeUserId: followeeUserID,
		})
		if err != nil {
			httpkit.WriteRPCError(writer, request, err)
			return
		}
		httpkit.WriteOK(writer, request, map[string]any{"following": resp.GetFollowing(), "follower_stats": overviewStatsFromProto(resp.GetFollowerStats())})
	case http.MethodDelete:
		resp, err := h.userSocialClient.UnfollowUser(request.Context(), &userv1.UnfollowUserRequest{
			Meta:           httpkit.UserMeta(request, ""),
			FollowerUserId: userID,
			FolloweeUserId: followeeUserID,
		})
		if err != nil {
			httpkit.WriteRPCError(writer, request, err)
			return
		}
		httpkit.WriteOK(writer, request, map[string]any{"following": resp.GetFollowing(), "follower_stats": overviewStatsFromProto(resp.GetFollowerStats())})
	default:
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
	}
}

func (h *Handler) applyFriend(writer http.ResponseWriter, request *http.Request, targetUserID int64) {
	if request.Method != http.MethodPost {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	resp, err := h.userSocialClient.ApplyFriend(request.Context(), &userv1.ApplyFriendRequest{
		Meta:            httpkit.UserMeta(request, ""),
		RequesterUserId: auth.UserIDFromContext(request.Context()),
		TargetUserId:    targetUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"application": friendApplicationDataFromProto(resp.GetApplication()), "already_friends": resp.GetAlreadyFriends()})
}

func (h *Handler) acceptFriendApplication(writer http.ResponseWriter, request *http.Request, requesterUserID int64) {
	if request.Method != http.MethodPost {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	resp, err := h.userSocialClient.AcceptFriendApplication(request.Context(), &userv1.AcceptFriendApplicationRequest{
		Meta:            httpkit.UserMeta(request, ""),
		AccepterUserId:  auth.UserIDFromContext(request.Context()),
		RequesterUserId: requesterUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"friend": friendData(resp.GetFriend())})
}

func (h *Handler) deleteFriend(writer http.ResponseWriter, request *http.Request, friendUserID int64) {
	if request.Method != http.MethodDelete {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	resp, err := h.userSocialClient.DeleteFriend(request.Context(), &userv1.DeleteFriendRequest{
		Meta:         httpkit.UserMeta(request, ""),
		UserId:       auth.UserIDFromContext(request.Context()),
		FriendUserId: friendUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{"deleted": resp.GetDeleted()})
}

func socialPage(request *http.Request) (int32, int32, bool) {
	page, ok := httpkit.PositiveInt32Query(request, "page", 1)
	if !ok {
		return 0, 0, false
	}
	pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20)
	if !ok {
		return 0, 0, false
	}
	return page, pageSize, true
}

func profileVisitData(record *userv1.ProfileVisitRecord) profileVisitRecordData {
	if record == nil {
		return profileVisitRecordData{}
	}
	data := profileVisitRecordData{
		VisitorIdentityHidden: record.GetVisitorIdentityHidden(),
		TargetUserID:          userIDString(record.GetTargetUserId()),
		VisitCount:            record.GetVisitCount(),
		LastVisitedAtMS:       record.GetLastVisitedAtMs(),
	}
	if record.GetVisitorIdentityHidden() {
		// 匿名记录只暴露随机展示 ID；visitor_user_id 使用 omitempty 完全省略，也不存在可继续拉取的 profile。
		data.AnonymousVisitID = record.GetAnonymousVisitId()
	} else {
		data.VisitorUserID = userIDString(record.GetVisitorUserId())
	}
	return data
}

func followData(record *userv1.FollowRecord) followRecordData {
	if record == nil {
		return followRecordData{}
	}
	return followRecordData{
		FollowerUserID: userIDString(record.GetFollowerUserId()),
		FolloweeUserID: userIDString(record.GetFolloweeUserId()),
		FollowedAtMS:   record.GetFollowedAtMs(),
	}
}

func friendData(record *userv1.FriendRecord) friendRecordData {
	if record == nil {
		return friendRecordData{}
	}
	return friendRecordData{
		UserID:       userIDString(record.GetUserId()),
		FriendUserID: userIDString(record.GetFriendUserId()),
		FriendedAtMS: record.GetFriendedAtMs(),
	}
}

func friendApplicationDataFromProto(application *userv1.FriendApplication) friendApplicationData {
	if application == nil {
		return friendApplicationData{}
	}
	return friendApplicationData{
		RequesterUserID: userIDString(application.GetRequesterUserId()),
		TargetUserID:    userIDString(application.GetTargetUserId()),
		Status:          application.GetStatus(),
		CreatedAtMS:     application.GetCreatedAtMs(),
		UpdatedAtMS:     application.GetUpdatedAtMs(),
	}
}
