package service

import (
	"context"
	"strings"
	"time"

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/tencentim"
	"hyapp/pkg/xerr"
	"hyapp/services/room-service/internal/room/command"
	"hyapp/services/room-service/internal/room/outbox"
	"hyapp/services/room-service/internal/room/rank"
	"hyapp/services/room-service/internal/room/state"
)

const defaultSystemEvictReason = "platform_moderation"

// SystemEvictUser 把平台封禁、风控命中或后台治理用户从当前 Room Cell 驱逐。
// 该命令绕过房间管理员权限矩阵，但仍必须经过 Room Cell、command log、outbox 和快照链路。
func (s *Service) SystemEvictUser(ctx context.Context, req *roomv1.SystemEvictUserRequest) (*roomv1.SystemEvictUserResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	targetUserID := req.GetTargetUserId()
	if targetUserID <= 0 {
		// 系统驱逐一定要有明确目标用户，不能依赖 actor 兜底误踢触发人。
		return nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
	}

	roomID := strings.TrimSpace(req.GetMeta().GetRoomId())
	if roomID == "" {
		// 上游不知道房间时从当前房间读模型定位；该读模型来自 RoomSnapshot 投影，不扫描所有 Cell。
		presence, exists, err := s.repository.GetCurrentRoomPresence(ctx, targetUserID)
		if err != nil {
			return nil, err
		}
		if !exists {
			return &roomv1.SystemEvictUserResponse{
				HadCurrentRoom: false,
				Result:         commandResult(false, 0, s.clock.Now()),
			}, nil
		}
		// presence 只提供候选房间，后续 mutateRoom 仍会恢复并校验 Room Cell 权威状态。
		roomID = presence.RoomID
	}

	base := baseFromMeta(req.GetMeta())
	base.Room = roomID
	if base.ActorID <= 0 {
		// mutateRoom 需要非零 actor 作为命令元信息；系统驱逐不使用它做房间权限判断。
		base.ActorID = targetUserID
	}
	cmd := command.SystemEvictUser{
		Base:           base,
		TargetUserID:   targetUserID,
		OperatorUserID: req.GetOperatorUserId(),
		Reason:         normalizeSystemEvictReason(req.GetReason()),
		BanFromRoom:    req.GetBanFromRoom(),
	}

	shouldKickRTC := false
	result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) {
		if err := requireActiveRoom(current); err != nil {
			// 关闭房间已经没有可驱逐的业务在线态，避免系统命令复活历史状态。
			return mutationResult{}, nil, err
		}

		_, inRoom := current.OnlineUsers[cmd.TargetUserID]
		_, onSeat := current.SeatByUser(cmd.TargetUserID)
		_, alreadyBanned := current.BanUsers[cmd.TargetUserID]
		if !inRoom && !onSeat && !current.AdminUsers[cmd.TargetUserID] && (!cmd.BanFromRoom || alreadyBanned) {
			// 重复系统驱逐保持幂等；没有残留状态时只返回当前快照。
			return mutationResult{snapshot: current.ToProto()}, nil, nil
		}
		// 只有用户确实还在业务房间或麦位上，才需要额外调用 RTC 服务端踢出媒体连接。
		shouldKickRTC = inRoom || onSeat

		var releasedSeatNo int32
		var releasedMicSessionID string
		if seat, exists := current.SeatByUser(cmd.TargetUserID); exists {
			// 系统驱逐释放麦位时同样写 mic down 事件，麦上时长统计不能只看 kick 事件。
			index := current.SeatIndex(seat.SeatNo)
			releasedSeatNo = seat.SeatNo
			releasedMicSessionID = current.MicSeats[index].MicSessionID
			current.ClearMicSession(index)
		}

		delete(current.OnlineUsers, cmd.TargetUserID)
		delete(current.AdminUsers, cmd.TargetUserID)
		if cmd.BanFromRoom {
			// BanFromRoom 由治理入口决定；只驱逐不封房间时，用户后续可按产品策略重新进房。
			current.BanUsers[cmd.TargetUserID] = state.UserModerationState{
				UserID:      cmd.TargetUserID,
				ActorUserID: 0,
				CreatedAtMS: now.UnixMilli(),
			}
		}
		current.Version++

		// 复用 RoomUserKicked 事实类型，让客户端和活动消费者不用区分管理踢人与系统驱逐的清场语义。
		evictEvent, err := outbox.Build(current.RoomID, "RoomUserKicked", current.Version, now, &roomeventsv1.RoomUserKicked{
			ActorUserId:  0,
			TargetUserId: cmd.TargetUserID,
		})
		if err != nil {
			return mutationResult{}, nil, err
		}
		records := make([]outbox.Record, 0, 2)
		if releasedMicSessionID != "" {
			micEvent, err := buildMicDownEventForSeat(current.RoomID, current.Version, now, 0, cmd.TargetUserID, releasedSeatNo, releasedMicSessionID, cmd.Reason)
			if err != nil {
				return mutationResult{}, nil, err
			}
			records = append(records, micEvent)
		}
		records = append(records, evictEvent)

		return mutationResult{
			snapshot: current.ToProto(),
			syncEvent: &tencentim.RoomEvent{
				EventID:      evictEvent.EventID,
				RoomID:       current.RoomID,
				EventType:    "room_user_kicked",
				TargetUserID: tencentim.FormatOptionalUserID(cmd.TargetUserID),
				RoomVersion:  current.Version,
				Attributes:   map[string]string{"reason": cmd.Reason},
			},
		}, records, nil
	})
	if err != nil {
		return nil, err
	}

	// Room Cell 提交后再做 RTC 踢人：RTC 是房间外副作用，失败不能回滚已经落盘的治理事实。
	response := &roomv1.SystemEvictUserResponse{
		HadCurrentRoom: true,
		Result:         commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
		Room:           result.snapshot,
		RoomId:         roomID,
	}
	if result.applied && shouldKickRTC && s.rtcUserRemover != nil {
		if err := s.rtcUserRemover.RemoveUserByStrRoomID(ctx, roomID, targetUserID); err != nil {
			response.RtcKickError = err.Error()
		} else {
			response.RtcKicked = true
		}
	}

	return response, nil
}

// normalizeSystemEvictReason 固定治理事件默认原因，避免空 reason 进入 outbox 和 IM 属性。
func normalizeSystemEvictReason(reason string) string {
	reason = strings.TrimSpace(reason)
	if reason == "" {
		return defaultSystemEvictReason
	}
	return reason
}
