// Package command 定义 room-service 可持久化、可回放的房间命令模型。
package command

import (
	"encoding/json"
	"fmt"
)

// Command 统一表达可写入 command log 的房间命令。
type Command interface {
	// ID 返回命令幂等键，通常来自 gateway 的 RequestMeta.command_id。
	ID() string
	// RoomID 返回命令所属房间，Room Cell 路由和 command log 分区依赖它。
	RoomID() string
	// ActorUserID 返回命令操作者，不能由具体命令字段重复推导。
	ActorUserID() int64
	// Type 返回稳定命令类型，持久化和恢复反序列化都依赖该字符串。
	Type() string
}

// Base 承载所有房间命令都共享的元信息。
type Base struct {
	// AppCode 是多 App 租户键；同一个 room_id 在不同 App 下必须完全隔离。
	AppCode string `json:"app_code"`
	// RequestID 是入口请求 ID，用于追踪一次外部调用。
	RequestID string `json:"request_id"`
	// CommandID 是幂等主键，重复命令必须返回已提交状态而不是重复变更房间。
	CommandID string `json:"command_id"`
	// ActorID 是操作者用户 ID，房间权限和 presence 校验以它为准。
	ActorID int64 `json:"actor_user_id"`
	// Room 是目标房间 ID，命令只允许作用于一个 Room Cell。
	Room string `json:"room_id"`
	// GatewayNodeID 记录入口节点，便于排查命令来源。
	GatewayNodeID string `json:"gateway_node_id"`
	// SessionID 记录 gateway 看到的客户端会话，不等同于腾讯云 IM 连接。
	SessionID string `json:"session_id"`
	// SentAtMS 是客户端请求进入系统的时间，恢复回放时用于还原用户态时间字段。
	SentAtMS int64 `json:"sent_at_ms"`
}

// ID 返回命令幂等主键。
func (b Base) ID() string {
	return b.CommandID
}

// RoomID 返回命令所属房间。
func (b Base) RoomID() string {
	return b.Room
}

// ActorUserID 返回命令操作者。
func (b Base) ActorUserID() int64 {
	return b.ActorID
}

// CreateRoom 定义房间创建请求。
type CreateRoom struct {
	Base
	// OwnerUserID 是房间所有者，创建后自动进入管理员集合。
	OwnerUserID int64 `json:"owner_user_id"`
	// SeatCount 决定初始化麦位数量。
	SeatCount int32 `json:"seat_count"`
	// Mode 记录房间模式，当前版本只保存不做复杂策略分发。
	Mode string `json:"mode"`
	// VisibleRegionID 是创建时固定的房间列表区域，0 表示 GLOBAL。
	VisibleRegionID int64 `json:"visible_region_id"`
	// RoomName 是房间展示名称，恢复时回填 RoomExt["title"]，不拆成独立状态 owner。
	RoomName string `json:"room_name"`
	// RoomAvatar 是房间展示头像，恢复时回填 RoomExt["cover_url"]。
	RoomAvatar string `json:"room_avatar"`
	// RoomDescription 是房间简介，恢复时回填 RoomExt["description"]。
	RoomDescription string `json:"room_description"`
	// RoomShortID 是房间短 ID，首版等于创建者当前展示短号。
	RoomShortID string `json:"room_short_id"`
}

// Type 返回命令类型。
func (CreateRoom) Type() string { return "create_room" }

// UpdateRoomProfile 定义房主修改房间展示资料和麦位数量的请求。
type UpdateRoomProfile struct {
	Base
	// RoomName 非 nil 表示更新房间展示名称；空字符串由服务层校验拒绝。
	RoomName *string `json:"room_name,omitempty"`
	// RoomAvatar 非 nil 表示更新房间头像；空字符串会恢复为系统默认头像。
	RoomAvatar *string `json:"room_avatar,omitempty"`
	// RoomDescription 非 nil 表示更新房间简介，允许清空。
	RoomDescription *string `json:"room_description,omitempty"`
	// SeatCount 非 nil 表示调整麦位总数，值必须来自后台启用配置。
	SeatCount *int32 `json:"seat_count,omitempty"`
}

// Type 返回命令类型。
func (UpdateRoomProfile) Type() string { return "update_room_profile" }

// JoinRoom 定义业务进房请求。
type JoinRoom struct {
	Base
	// Role 是用户在房间内的轻量角色，默认 audience。
	Role string `json:"role"`
}

// Type 返回命令类型。
func (JoinRoom) Type() string { return "join_room" }

// RoomHeartbeat 定义房间业务 presence 显式心跳。
type RoomHeartbeat struct {
	Base
}

// Type 返回命令类型。
func (RoomHeartbeat) Type() string { return "room_heartbeat" }

// LeaveRoom 定义业务离房请求。
type LeaveRoom struct {
	Base
}

// Type 返回命令类型。
func (LeaveRoom) Type() string { return "leave_room" }

// CloseRoom 定义房间关闭请求。
type CloseRoom struct {
	Base
	// Reason 记录关闭来源，例如 closed_by_owner 或 admin_closed。
	Reason string `json:"reason,omitempty"`
}

// Type 返回命令类型。
func (CloseRoom) Type() string { return "close_room" }

// MicUp 定义上麦请求。
type MicUp struct {
	Base
	// SeatNo 是目标麦位编号，从 RoomState 初始化的麦位集合中查找。
	SeatNo int32 `json:"seat_no"`
	// MicSessionID 是服务端为本次上麦生成的发流会话 ID，确认和超时释放都必须匹配它。
	MicSessionID string `json:"mic_session_id"`
	// PublishDeadlineMS 是客户端必须确认 RTC 发流成功的截止时间。
	PublishDeadlineMS int64 `json:"publish_deadline_ms"`
}

// Type 返回命令类型。
func (MicUp) Type() string { return "mic_up" }

// MicDown 定义下麦请求。
type MicDown struct {
	Base
	// TargetUserID 是被下麦用户；操作者和目标用户可以不同。
	TargetUserID int64 `json:"target_user_id"`
	// MicSessionID 非空时只允许释放同一发流会话，防止旧超时任务清掉新上麦。
	MicSessionID string `json:"mic_session_id,omitempty"`
	// Reason 记录系统触发下麦原因，例如 publish_timeout；主动下麦保持为空。
	Reason string `json:"reason,omitempty"`
}

// Type 返回命令类型。
func (MicDown) Type() string { return "mic_down" }

// ConfirmMicPublishing 定义 RTC 发流成功确认请求。
type ConfirmMicPublishing struct {
	Base
	// TargetUserID 是被确认发布音频的用户；为空时由服务层使用 ActorUserID。
	TargetUserID int64 `json:"target_user_id"`
	// MicSessionID 必须匹配当前麦位上的会话。
	MicSessionID string `json:"mic_session_id"`
	// RoomVersion 是触发客户端/RTC 事件时看到的房间版本，旧版本事件会被忽略。
	RoomVersion int64 `json:"room_version"`
	// EventTimeMS 是客户端 SDK 或 RTC webhook 的事件时间，必须晚于已接受事件。
	EventTimeMS int64 `json:"event_time_ms"`
	// Source 记录确认来源，例如 client 或 rtc_webhook。
	Source string `json:"source,omitempty"`
}

// Type 返回命令类型。
func (ConfirmMicPublishing) Type() string { return "confirm_mic_publishing" }

// SetMicMute 定义麦克风静音状态同步请求。
type SetMicMute struct {
	Base
	// TargetUserID 是被修改麦克风静音态的麦上用户。
	TargetUserID int64 `json:"target_user_id"`
	// Muted 为 true 表示麦克风静音，false 表示取消静音。
	Muted bool `json:"muted"`
}

// Type 返回命令类型。
func (SetMicMute) Type() string { return "set_mic_mute" }

// ApplyRTCEvent 定义由可信腾讯 RTC 服务端回调驱动的房间状态事件。
type ApplyRTCEvent struct {
	Base
	// TargetUserID 是腾讯 RTC 回调中的 UserId，必须映射回内部长 user_id。
	TargetUserID int64 `json:"target_user_id"`
	// EventType 是 gateway 标准化后的 RTC 事件，当前为 audio_started/audio_stopped/room_exited。
	EventType string `json:"event_type"`
	// EventTimeMS 是腾讯 RTC 回调中的 EventMsTs，用于丢弃旧事件。
	EventTimeMS int64 `json:"event_time_ms"`
	// Reason 保留腾讯回调 reason 和入口语义，便于 IM 房间消息和审计排障。
	Reason string `json:"reason,omitempty"`
	// Source 记录确认来源，当前为 tencent_rtc_callback。
	Source string `json:"source,omitempty"`
	// ExternalEventID 是由 gateway 从腾讯事件字段派生的稳定事件标识。
	ExternalEventID string `json:"external_event_id,omitempty"`
}

// Type 返回命令类型。
func (ApplyRTCEvent) Type() string { return "apply_rtc_event" }

// ChangeMicSeat 定义换麦位请求。
type ChangeMicSeat struct {
	Base
	// TargetUserID 是需要换位的用户。
	TargetUserID int64 `json:"target_user_id"`
	// SeatNo 是目标麦位编号。
	SeatNo int32 `json:"seat_no"`
}

// Type 返回命令类型。
func (ChangeMicSeat) Type() string { return "change_mic_seat" }

// SetMicSeatLock 定义锁定或解锁麦位请求。
type SetMicSeatLock struct {
	Base
	// SeatNo 是目标麦位编号。
	SeatNo int32 `json:"seat_no"`
	// Locked 为 true 表示锁定，false 表示解锁。
	Locked bool `json:"locked"`
}

// Type 返回命令类型。
func (SetMicSeatLock) Type() string { return "set_mic_seat_lock" }

// SetChatEnabled 定义房间公屏开关请求。
type SetChatEnabled struct {
	Base
	// Enabled 为 false 时 CheckSpeakPermission 会拒绝所有普通公屏发言。
	Enabled bool `json:"enabled"`
}

// Type 返回命令类型。
func (SetChatEnabled) Type() string { return "set_chat_enabled" }

// SetRoomAdmin 定义添加或移除管理员请求。
type SetRoomAdmin struct {
	Base
	// TargetUserID 是被添加或移除管理员身份的用户。
	TargetUserID int64 `json:"target_user_id"`
	// Enabled 为 true 表示添加管理员，false 表示移除管理员。
	Enabled bool `json:"enabled"`
}

// Type 返回命令类型。
func (SetRoomAdmin) Type() string { return "set_room_admin" }

// MuteUser 定义禁言请求。
type MuteUser struct {
	Base
	// TargetUserID 是被修改禁言状态的用户。
	TargetUserID int64 `json:"target_user_id"`
	// Muted 为 true 表示禁言，false 表示解除禁言。
	Muted bool `json:"muted"`
}

// Type 返回命令类型。
func (MuteUser) Type() string { return "mute_user" }

// KickUser 定义踢人请求。
type KickUser struct {
	Base
	// TargetUserID 是被踢出并加入 ban 集合的用户。
	TargetUserID int64 `json:"target_user_id"`
}

// Type 返回命令类型。
func (KickUser) Type() string { return "kick_user" }

// SystemEvictUser 定义平台级封禁、风控或后台治理触发的房间驱逐请求。
type SystemEvictUser struct {
	Base
	// TargetUserID 是被系统驱逐的房间用户。
	TargetUserID int64 `json:"target_user_id"`
	// OperatorUserID 是触发治理动作的后台管理员或 App 管理员，不参与房间权限判断。
	OperatorUserID int64 `json:"operator_user_id"`
	// Reason 记录封禁、风控或后台动作来源，进入 command log 便于审计追踪。
	Reason string `json:"reason"`
	// BanFromRoom 控制是否写入当前房间 ban 集合。
	BanFromRoom bool `json:"ban_from_room"`
}

// Type 返回命令类型。
func (SystemEvictUser) Type() string { return "system_evict_user" }

// UnbanUser 定义解除房间 ban 请求。
type UnbanUser struct {
	Base
	// TargetUserID 是被解除 ban 的用户。
	TargetUserID int64 `json:"target_user_id"`
}

// Type 返回命令类型。
func (UnbanUser) Type() string { return "unban_user" }

// SendGift 定义送礼请求。
type SendGift struct {
	Base
	// TargetType 当前只支持 user；保留字段避免后续 all_mic/all_room/couple 再改命令结构。
	TargetType string `json:"target_type"`
	// TargetUserIDs 是新协议目标用户集合；当前 target_type=user 时只允许一个用户。
	TargetUserIDs []int64 `json:"target_user_ids,omitempty"`
	// TargetUserID 是礼物接收方，当前版本要求接收方在房间内。
	TargetUserID int64 `json:"target_user_id"`
	// GiftID 是礼物配置标识，具体配置有效性当前由调用方和 wallet 链路保证。
	GiftID string `json:"gift_id"`
	// GiftCount 是礼物数量，必须为正数。
	GiftCount int32 `json:"gift_count"`
	// BillingReceiptID 是 wallet-service 成功落账后的回执，用于排障和恢复审计。
	BillingReceiptID string `json:"billing_receipt_id,omitempty"`
	// CoinSpent 是 sender COIN 实际扣减值，来自 wallet-service 服务端价格。
	CoinSpent int64 `json:"coin_spent,omitempty"`
	// GiftPointAdded 是 target GIFT_POINT 实际入账值，来自 wallet-service。
	GiftPointAdded int64 `json:"gift_point_added,omitempty"`
	// HeatValue 是 Room Cell 恢复时唯一可信的热度增量，不能重新用客户端价格推导。
	HeatValue int64 `json:"heat_value,omitempty"`
	// PriceVersion 是本次钱包结算使用的服务端礼物价格版本。
	PriceVersion string `json:"price_version,omitempty"`
}

// Type 返回命令类型。
func (SendGift) Type() string { return "send_gift" }

// Serialize 把命令编码成 command log 持久化所需的稳定载荷。
func Serialize(cmd Command) ([]byte, error) {
	// 当前 command log 使用 JSON 载荷，便于排查；命令类型由独立列保存。
	return json.Marshal(cmd)
}

// IdempotencyPayloadForCommand 生成用于 command_id 冲突判定的业务载荷。
func IdempotencyPayloadForCommand(cmd Command) ([]byte, error) {
	payload, err := Serialize(cmd)
	if err != nil {
		return nil, err
	}
	return IdempotencyPayload(payload)
}

// IdempotencyPayload 去掉请求追踪和入口节点等易变字段，只保留业务命令语义。
func IdempotencyPayload(payload []byte) ([]byte, error) {
	var values map[string]any
	if err := json.Unmarshal(payload, &values); err != nil {
		return nil, err
	}
	delete(values, "request_id")
	delete(values, "gateway_node_id")
	delete(values, "session_id")
	delete(values, "sent_at_ms")
	// SendGift 的以下字段由 wallet-service 结算后写入 command log，不属于客户端请求幂等语义。
	delete(values, "billing_receipt_id")
	delete(values, "coin_spent")
	delete(values, "gift_point_added")
	delete(values, "heat_value")
	delete(values, "price_version")
	// MicUp 的发流确认 deadline 由 room-service 生成，客户端重试同一 command_id 时不能因此冲突。
	delete(values, "publish_deadline_ms")

	return json.Marshal(values)
}

// Deserialize 按命令类型恢复命令载荷。
func Deserialize(commandType string, payload []byte) (Command, error) {
	var cmd Command

	// command_type 是恢复时的分发键；未知类型表示当前代码无法保证恢复语义。
	switch commandType {
	case CreateRoom{}.Type():
		cmd = &CreateRoom{}
	case UpdateRoomProfile{}.Type():
		cmd = &UpdateRoomProfile{}
	case JoinRoom{}.Type():
		cmd = &JoinRoom{}
	case RoomHeartbeat{}.Type():
		cmd = &RoomHeartbeat{}
	case LeaveRoom{}.Type():
		cmd = &LeaveRoom{}
	case CloseRoom{}.Type():
		cmd = &CloseRoom{}
	case MicUp{}.Type():
		cmd = &MicUp{}
	case MicDown{}.Type():
		cmd = &MicDown{}
	case ConfirmMicPublishing{}.Type():
		cmd = &ConfirmMicPublishing{}
	case SetMicMute{}.Type():
		cmd = &SetMicMute{}
	case ApplyRTCEvent{}.Type():
		cmd = &ApplyRTCEvent{}
	case ChangeMicSeat{}.Type():
		cmd = &ChangeMicSeat{}
	case SetMicSeatLock{}.Type():
		cmd = &SetMicSeatLock{}
	case SetChatEnabled{}.Type():
		cmd = &SetChatEnabled{}
	case SetRoomAdmin{}.Type():
		cmd = &SetRoomAdmin{}
	case MuteUser{}.Type():
		cmd = &MuteUser{}
	case KickUser{}.Type():
		cmd = &KickUser{}
	case SystemEvictUser{}.Type():
		cmd = &SystemEvictUser{}
	case UnbanUser{}.Type():
		cmd = &UnbanUser{}
	case SendGift{}.Type():
		cmd = &SendGift{}
	default:
		return nil, fmt.Errorf("unknown room command type: %s", commandType)
	}

	if err := json.Unmarshal(payload, cmd); err != nil {
		// 单条命令反序列化失败说明 command log 损坏，恢复流程必须失败而不是继续构建错误状态。
		return nil, err
	}

	return cmd, nil
}
