package integration

import (
	"context"
	"fmt"

	"google.golang.org/protobuf/proto"
	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/tencentim"
)

// TencentIMPublisher 是 room-service 到腾讯云 IM 的生产路径发布器。
type TencentIMPublisher struct {
	// client 持有腾讯云 IM REST API、UserSig 和管理员账号配置。
	client *tencentim.RESTClient
}

// NewTencentIMPublisher 创建腾讯云 IM 房间消息发布器。
func NewTencentIMPublisher(client *tencentim.RESTClient) *TencentIMPublisher {
	return &TencentIMPublisher{client: client}
}

// EnsureRoomGroup 确保每个 Room Cell 都有对应腾讯云 IM 群组。
func (p *TencentIMPublisher) EnsureRoomGroup(ctx context.Context, roomID string, ownerUserID int64) error {
	if p == nil || p.client == nil {
		// 未启用腾讯云 IM 时保持无副作用，便于本地测试只跑 room-service。
		return nil
	}

	return p.client.EnsureRoomGroup(ctx, roomID, ownerUserID)
}

// PublishRoomEvent 把房间系统事件发送到腾讯云 IM 群。
func (p *TencentIMPublisher) PublishRoomEvent(ctx context.Context, event tencentim.RoomEvent) error {
	if p == nil || p.client == nil {
		// 未启用腾讯云 IM 时不阻断房间状态提交。
		return nil
	}

	return p.client.PublishRoomEvent(ctx, event)
}

// PublishOutboxEvent 补偿投递 room_outbox 中可转成房间系统消息的事件。
func (p *TencentIMPublisher) PublishOutboxEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error {
	if p == nil || p.client == nil || envelope == nil {
		return nil
	}
	if envelope.GetEventType() == "RoomCreated" {
		var body roomeventsv1.RoomCreated
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return err
		}
		// 建群是腾讯 IM 的外部副作用，必须留在 outbox worker 内，不能阻塞 CreateRoom 返回。
		return p.client.EnsureRoomGroup(ctx, envelope.GetRoomId(), body.GetOwnerUserId())
	}

	event, publish, err := roomEventFromEnvelope(envelope)
	if err != nil || !publish {
		return err
	}
	if envelope.GetEventType() == "RoomUserKicked" {
		// KickUser 的安全边界除了系统消息，还必须补偿移除腾讯云 IM 群成员。
		if err := p.client.DeleteRoomGroupMember(ctx, envelope.GetRoomId(), event.TargetUserID); err != nil {
			return err
		}
	}

	return p.client.PublishRoomEvent(ctx, event)
}

// noopRoomEventPublisher 是本地和测试路径的房间消息发布占位实现。
type noopRoomEventPublisher struct{}

// NewNoopRoomEventPublisher 返回无外部副作用的房间消息发布器。
func NewNoopRoomEventPublisher() RoomEventPublisher {
	return noopRoomEventPublisher{}
}

// EnsureRoomGroup 在 noop 实现中不做外部调用。
func (noopRoomEventPublisher) EnsureRoomGroup(context.Context, string, int64) error {
	return nil
}

// PublishRoomEvent 在 noop 实现中不做外部调用。
func (noopRoomEventPublisher) PublishRoomEvent(context.Context, tencentim.RoomEvent) error {
	return nil
}

// noopOutboxPublisher 是首版异步 outbox 消费占位实现。
type noopOutboxPublisher struct{}

// NewNoopOutboxPublisher 返回一个占位 outbox 发布器。
// 当前版本保留 outbox 表和 worker 语义，activity/audit 投递后续替换该实现。
func NewNoopOutboxPublisher() OutboxPublisher {
	return noopOutboxPublisher{}
}

// PublishOutboxEvent 直接吞掉 outbox 事件。
func (noopOutboxPublisher) PublishOutboxEvent(context.Context, *roomeventsv1.EventEnvelope) error {
	// room_outbox worker 会把同一事件源异步投给腾讯云 IM、activity 或后续审计消费者。
	return nil
}

func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.RoomEvent, bool, error) {
	// EventEnvelope 是 room_outbox 的稳定信封，补偿投递时再按 event_type 解出客户端字段。
	base := tencentim.RoomEvent{
		EventID:     envelope.GetEventId(),
		RoomID:      envelope.GetRoomId(),
		EventType:   eventTypeForClient(envelope.GetEventType()),
		RoomVersion: envelope.GetRoomVersion(),
	}

	switch envelope.GetEventType() {
	case "RoomUserJoined":
		// 进房事件需要带 role，客户端可用于展示进房身份，但权限仍以后续 snapshot 为准。
		var body roomeventsv1.RoomUserJoined
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetUserId()
		base.TargetUserID = body.GetUserId()
		base.Attributes = map[string]string{"role": body.GetRole()}
		return base, true, nil
	case "RoomUserLeft":
		// 离房事件只表达业务 presence 移除，不声明腾讯云 IM 连接是否已断开。
		var body roomeventsv1.RoomUserLeft
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetUserId()
		base.TargetUserID = body.GetUserId()
		return base, true, nil
	case "RoomMicChanged":
		// 麦位变更把 from/to 合并成一个客户端事件，避免换麦出现中间空位状态。
		var body roomeventsv1.RoomMicChanged
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.TargetUserID = body.GetTargetUserId()
		base.SeatNo = body.GetToSeat()
		if base.SeatNo == 0 {
			base.SeatNo = body.GetFromSeat()
		}
		base.Attributes = map[string]string{
			"action":                body.GetAction(),
			"mic_session_id":        body.GetMicSessionId(),
			"publish_state":         body.GetPublishState(),
			"reason":                body.GetReason(),
			"publish_deadline_ms":   fmt.Sprintf("%d", body.GetPublishDeadlineMs()),
			"publish_event_time_ms": fmt.Sprintf("%d", body.GetPublishEventTimeMs()),
			"mic_muted":             fmt.Sprintf("%t", body.GetMicMuted()),
		}
		return base, true, nil
	case "RoomMicSeatLocked":
		// 锁麦事件只广播锁状态；首版锁麦不会自动下掉已占用用户。
		var body roomeventsv1.RoomMicSeatLocked
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.SeatNo = body.GetSeatNo()
		base.Attributes = map[string]string{"locked": fmt.Sprintf("%t", body.GetLocked())}
		return base, true, nil
	case "RoomProfileUpdated":
		// 房间资料变更会影响标题、头像、简介和麦位布局，客户端收到后应以 room_version 更新本地 UI。
		var body roomeventsv1.RoomProfileUpdated
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.Attributes = map[string]string{
			"room_name":        body.GetRoomName(),
			"room_avatar":      body.GetRoomAvatar(),
			"room_description": body.GetRoomDescription(),
			"seat_count":       fmt.Sprintf("%d", body.GetSeatCount()),
		}
		return base, true, nil
	case "RoomChatEnabledChanged":
		// 公屏开关事件提示客户端更新输入态，真正发言仍由 CheckSpeakPermission 拦截。
		var body roomeventsv1.RoomChatEnabledChanged
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.Attributes = map[string]string{"enabled": fmt.Sprintf("%t", body.GetEnabled())}
		return base, true, nil
	case "RoomAdminChanged":
		// 管理员变更进入系统消息，客户端收到后仍应以最新 snapshot 修正本地权限 UI。
		var body roomeventsv1.RoomAdminChanged
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.TargetUserID = body.GetTargetUserId()
		base.Attributes = map[string]string{"enabled": fmt.Sprintf("%t", body.GetEnabled())}
		return base, true, nil
	case "RoomUserMuted":
		// 禁言事件只影响公屏发言权限，不改变 presence、麦位或收消息资格。
		var body roomeventsv1.RoomUserMuted
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.TargetUserID = body.GetTargetUserId()
		base.Attributes = map[string]string{"muted": fmt.Sprintf("%t", body.GetMuted())}
		return base, true, nil
	case "RoomUserKicked":
		// KickUser 的即时系统消息和补偿踢群共用同一个 target_user_id。
		var body roomeventsv1.RoomUserKicked
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.TargetUserID = body.GetTargetUserId()
		return base, true, nil
	case "RoomUserUnbanned":
		// 解封只恢复再次 JoinRoom 的资格，不代表目标已经回到房间 presence。
		var body roomeventsv1.RoomUserUnbanned
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetActorUserId()
		base.TargetUserID = body.GetTargetUserId()
		return base, true, nil
	case "RoomGiftSent":
		// 送礼客户端消息只用 GiftSent 主事件承载，Heat/Rank 变化作为字段随同一条消息展示。
		var body roomeventsv1.RoomGiftSent
		if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil {
			return tencentim.RoomEvent{}, false, err
		}
		base.ActorUserID = body.GetSenderUserId()
		base.TargetUserID = body.GetTargetUserId()
		base.GiftValue = body.GetGiftValue()
		base.Attributes = map[string]string{
			"gift_id":            body.GetGiftId(),
			"gift_count":         fmt.Sprintf("%d", body.GetGiftCount()),
			"billing_receipt_id": body.GetBillingReceiptId(),
		}
		return base, true, nil
	default:
		// RoomCreated/HeatChanged/RankChanged 不单独推客户端，避免一条命令产生多条系统消息。
		return tencentim.RoomEvent{}, false, nil
	}
}

func eventTypeForClient(eventType string) string {
	// protobuf 事件名使用 PascalCase，客户端 TIMCustomElem 使用 snake_case 作为稳定协议字段。
	switch eventType {
	case "RoomUserJoined":
		return "room_user_joined"
	case "RoomUserLeft":
		return "room_user_left"
	case "RoomMicChanged":
		return "room_mic_changed"
	case "RoomMicSeatLocked":
		return "room_mic_seat_locked"
	case "RoomProfileUpdated":
		return "room_profile_updated"
	case "RoomChatEnabledChanged":
		return "room_chat_enabled_changed"
	case "RoomAdminChanged":
		return "room_admin_changed"
	case "RoomUserMuted":
		return "room_user_muted"
	case "RoomUserKicked":
		return "room_user_kicked"
	case "RoomUserUnbanned":
		return "room_user_unbanned"
	case "RoomGiftSent":
		return "room_gift_sent"
	default:
		return eventType
	}
}
