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 removes a platform-banned or risk-blocked user from their current Room Cell.
// It deliberately bypasses room manager role checks, but still serializes through the Room Cell command log.
func (s *Service) SystemEvictUser(ctx context.Context, req *roomv1.SystemEvictUserRequest) (*roomv1.SystemEvictUserResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	targetUserID := req.GetTargetUserId()
	if targetUserID <= 0 {
		return nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
	}

	roomID := strings.TrimSpace(req.GetMeta().GetRoomId())
	if roomID == "" {
		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
		}
		roomID = presence.RoomID
	}

	base := baseFromMeta(req.GetMeta())
	base.Room = roomID
	if base.ActorID <= 0 {
		// mutateRoom requires a non-zero actor for idempotency metadata; system eviction does not use it for authorization.
		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
		}
		shouldKickRTC = inRoom || onSeat

		var releasedSeatNo int32
		var releasedMicSessionID string
		if seat, exists := current.SeatByUser(cmd.TargetUserID); exists {
			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 {
			current.BanUsers[cmd.TargetUserID] = true
		}
		current.Version++

		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: cmd.TargetUserID,
				RoomVersion:  current.Version,
				Attributes:   map[string]string{"reason": cmd.Reason},
			},
		}, records, nil
	})
	if err != nil {
		return nil, err
	}

	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
}

func normalizeSystemEvictReason(reason string) string {
	reason = strings.TrimSpace(reason)
	if reason == "" {
		return defaultSystemEvictReason
	}
	return reason
}
