package service

import (
	"context"
	"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"
)

// SetMicSeatLock 锁定或解锁指定麦位；首版只允许锁空麦位。
func (s *Service) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	// 锁麦是房间管理命令，客户端不能通过麦位字段直接绕过 Room Cell 权限矩阵。
	cmd := command.SetMicSeatLock{
		Base:   baseFromMeta(req.GetMeta()),
		SeatNo: req.GetSeatNo(),
		Locked: req.GetLocked(),
	}

	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
		}
		if err := requireManagerPresent(current, cmd.ActorUserID()); err != nil {
			return mutationResult{}, nil, err
		}

		index := current.SeatIndex(cmd.SeatNo)
		if index < 0 {
			// 只能锁定房间初始化时存在的麦位编号。
			return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "seat does not exist")
		}
		if cmd.Locked && current.MicSeats[index].UserID != 0 {
			// 首版不把“下麦”和“锁麦”合并成一个命令，避免客户端处理复合事件。
			return mutationResult{}, nil, xerr.New(xerr.Conflict, "seat is occupied")
		}
		if current.MicSeats[index].Locked == cmd.Locked {
			// 重复锁定或重复解锁是 no-op，只写 command log 做幂等保护，不广播事件。
			return mutationResult{snapshot: current.ToProto()}, nil, nil
		}

		// 锁状态属于 Room Cell 核心态，必须和版本递增一起提交。
		current.MicSeats[index].Locked = cmd.Locked
		current.Version++

		// 锁麦事件进入 outbox，客户端通过系统消息修正麦位 UI。
		lockEvent, err := outbox.Build(current.RoomID, "RoomMicSeatLocked", current.Version, now, &roomeventsv1.RoomMicSeatLocked{
			ActorUserId: cmd.ActorUserID(),
			SeatNo:      cmd.SeatNo,
			Locked:      cmd.Locked,
		})
		if err != nil {
			return mutationResult{}, nil, err
		}

		return mutationResult{
			snapshot: current.ToProto(),
			syncEvent: &tencentim.RoomEvent{
				EventID:     lockEvent.EventID,
				RoomID:      current.RoomID,
				EventType:   "room_mic_seat_locked",
				ActorUserID: tencentim.FormatOptionalUserID(cmd.ActorUserID()),
				SeatNo:      cmd.SeatNo,
				RoomVersion: current.Version,
				Attributes: map[string]string{
					"locked": boolString(cmd.Locked),
				},
			},
		}, []outbox.Record{lockEvent}, nil
	})
	if err != nil {
		return nil, err
	}

	return &roomv1.SetMicSeatLockResponse{
		Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
		Room:   result.snapshot,
	}, nil
}

// SetChatEnabled 开启或关闭公屏发言守卫。
func (s *Service) SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	// 公屏开关只影响普通客户端发言守卫，不阻断 room-service 发送系统消息。
	cmd := command.SetChatEnabled{
		Base:    baseFromMeta(req.GetMeta()),
		Enabled: req.GetEnabled(),
	}

	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
		}
		if err := requireManagerPresent(current, cmd.ActorUserID()); err != nil {
			return mutationResult{}, nil, err
		}
		if current.ChatEnabled == cmd.Enabled {
			// 重复设置同一个开关值不产生事件，避免客户端收到无意义的状态变更。
			return mutationResult{snapshot: current.ToProto()}, nil, nil
		}

		// CheckSpeakPermission 后续会以该字段作为 chat_disabled 的权威来源。
		current.ChatEnabled = cmd.Enabled
		current.Version++

		// 公屏开关变化需要进入 outbox，便于 IM 系统消息和审计消费。
		chatEvent, err := outbox.Build(current.RoomID, "RoomChatEnabledChanged", current.Version, now, &roomeventsv1.RoomChatEnabledChanged{
			ActorUserId: cmd.ActorUserID(),
			Enabled:     cmd.Enabled,
		})
		if err != nil {
			return mutationResult{}, nil, err
		}

		return mutationResult{
			snapshot: current.ToProto(),
			syncEvent: &tencentim.RoomEvent{
				EventID:     chatEvent.EventID,
				RoomID:      current.RoomID,
				EventType:   "room_chat_enabled_changed",
				ActorUserID: tencentim.FormatOptionalUserID(cmd.ActorUserID()),
				RoomVersion: current.Version,
				Attributes: map[string]string{
					"enabled": boolString(cmd.Enabled),
				},
			},
		}, []outbox.Record{chatEvent}, nil
	})
	if err != nil {
		return nil, err
	}

	return &roomv1.SetChatEnabledResponse{
		Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
		Room:   result.snapshot,
	}, nil
}

// SetRoomAdmin 添加或移除房间管理员；只有 owner 可以执行。
func (s *Service) SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) {
	ctx = contextFromMeta(ctx, req.GetMeta())
	// 管理员身份是 RoomState 持久态，离房后保留，重新进房后继续生效。
	cmd := command.SetRoomAdmin{
		Base:         baseFromMeta(req.GetMeta()),
		TargetUserID: req.GetTargetUserId(),
		Enabled:      req.GetEnabled(),
	}

	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
		}
		if cmd.TargetUserID <= 0 {
			return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required")
		}
		if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil {
			// 只有 owner 可以增删 admin，避免管理员扩张自己的权限。
			return mutationResult{}, nil, err
		}
		if _, exists := current.OnlineUsers[cmd.TargetUserID]; !exists {
			// 首版不支持预设管理员，避免把不存在或已离房用户加入管理集合。
			return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not in room")
		}
		if cmd.TargetUserID == current.OwnerUserID {
			// owner 不是管理员，不能对 owner 执行设置或取消管理员。
			return mutationResult{}, nil, xerr.New(xerr.Conflict, "owner cannot be room admin target")
		}
		if current.AdminUsers[cmd.TargetUserID] == cmd.Enabled {
			// 重复添加或重复移除是 no-op，避免重复发送 RoomAdminChanged。
			return mutationResult{snapshot: current.ToProto()}, nil, nil
		}

		if cmd.Enabled {
			// map set 是管理员集合的恢复来源，快照会导出为 admin_user_ids。
			current.AdminUsers[cmd.TargetUserID] = true
		} else {
			delete(current.AdminUsers, cmd.TargetUserID)
		}
		current.Version++

		// 管理员变更事件同时服务客户端提示和后续房间审计。
		adminEvent, err := outbox.Build(current.RoomID, "RoomAdminChanged", current.Version, now, &roomeventsv1.RoomAdminChanged{
			ActorUserId:  cmd.ActorUserID(),
			TargetUserId: cmd.TargetUserID,
			Enabled:      cmd.Enabled,
		})
		if err != nil {
			return mutationResult{}, nil, err
		}

		return mutationResult{
			snapshot: current.ToProto(),
			syncEvent: &tencentim.RoomEvent{
				EventID:      adminEvent.EventID,
				RoomID:       current.RoomID,
				EventType:    "room_admin_changed",
				ActorUserID:  tencentim.FormatOptionalUserID(cmd.ActorUserID()),
				TargetUserID: tencentim.FormatOptionalUserID(cmd.TargetUserID),
				RoomVersion:  current.Version,
				Attributes: map[string]string{
					"enabled": boolString(cmd.Enabled),
				},
			},
		}, []outbox.Record{adminEvent}, nil
	})
	if err != nil {
		return nil, err
	}

	return &roomv1.SetRoomAdminResponse{
		Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()),
		Room:   result.snapshot,
	}, nil
}
