use std::collections::HashMap;

use uuid::Uuid;

use crate::event::{RoomInfo, UserInfo, message, reaction};
use crate::{ChannelBus, ChannelError, ChannelResult};

use super::WsHandler;

impl WsHandler {
    pub(super) async fn ensure_room_access(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
    ) -> ChannelResult<()> {
        let rooms = crate::rooms::user_rooms(
            &bus.inner.db,
            &bus.inner.cache,
            &bus.inner.config,
            user_id,
        )
        .await?;
        if rooms.contains(&room) {
            Ok(())
        } else {
            Err(ChannelError::AccessDenied)
        }
    }

    pub(super) async fn ensure_message_in_room(
        bus: &ChannelBus,
        room: Uuid,
        message: Uuid,
    ) -> ChannelResult<()> {
        let exists: Option<(Uuid,)> = db::sqlx::query_as(
            "SELECT id FROM room_message WHERE id = $1 AND room = $2 AND deleted_at IS NULL",
        )
        .bind(message)
        .bind(room)
        .fetch_optional(bus.inner.db.reader())
        .await?;
        exists.map(|_| ()).ok_or(ChannelError::RoomNotFound)
    }

    pub(super) async fn reaction_groups_for_messages(
        bus: &ChannelBus,
        user_id: Uuid,
        message_ids: &[Uuid],
    ) -> ChannelResult<HashMap<Uuid, Vec<reaction::ReactionGroup>>> {
        if message_ids.is_empty() {
            return Ok(HashMap::new());
        }

        let rows = db::sqlx::query_as::<_, (Uuid, String, Uuid)>(
            "SELECT message, reaction, \"user\" FROM room_reaction \
             WHERE message = ANY($1) ORDER BY created_at ASC",
        )
        .bind(message_ids)
        .fetch_all(bus.inner.db.reader())
        .await?;

        let user_ids: Vec<Uuid> =
            rows.iter().map(|(_, _, user)| *user).collect();
        let users = bus.lookup_users(&user_ids).await.unwrap_or_default();
        let mut grouped: HashMap<
            Uuid,
            HashMap<String, reaction::ReactionGroup>,
        > = HashMap::new();

        for (message_id, emoji, reactor) in rows {
            let group = grouped
                .entry(message_id)
                .or_default()
                .entry(emoji.clone())
                .or_insert_with(|| reaction::ReactionGroup {
                    emoji: emoji.clone(),
                    count: 0,
                    reacted_by_me: false,
                    users: Vec::new(),
                });
            group.count += 1;
            group.reacted_by_me |= reactor == user_id;
            group.users.push(
                users
                    .get(&reactor)
                    .cloned()
                    .unwrap_or_else(|| UserInfo::unknown(reactor)),
            );
        }

        Ok(grouped
            .into_iter()
            .map(|(message_id, groups)| {
                (message_id, groups.into_values().collect::<Vec<_>>())
            })
            .collect())
    }

    pub(super) async fn ensure_workspace_member(
        bus: &ChannelBus,
        user_id: Uuid,
        wk: Uuid,
    ) -> ChannelResult<()> {
        let row: Option<(Uuid,)> = db::sqlx::query_as(
            "SELECT wk FROM wk_member WHERE wk = $1 AND \"user\" = $2 AND leave_at IS NULL",
        )
        .bind(wk)
        .bind(user_id)
        .fetch_optional(bus.inner.db.reader())
        .await?;
        row.map(|_| ()).ok_or(ChannelError::AccessDenied)
    }

    #[allow(dead_code)]
    pub(super) fn missed_message_data(
        m: crate::MissedMessage,
    ) -> message::MessageNewService {
        message::MessageNewService {
            id: m.message_id,
            seq: m.seq,
            room: RoomInfo::unknown(m.room_id),
            sender_type: "user".to_string(),
            sender: UserInfo::unknown(m.sender_id),
            thread: None,
            in_reply_to: None,
            content: m.content,
            content_type: "text".to_string(),
            pinned: false,
            system_type: None,
            metadata: serde_json::Value::Null,
            thinking_content: None,
            thinking_is_chunked: None,
            send_at: m.send_at,
            reactions: vec![],
        }
    }
    #[allow(dead_code)]
    pub(super) fn message_data(
        m: model::channel::RoomMessageModel,
    ) -> message::MessageNewService {
        message::MessageNewService {
            id: m.id,
            seq: m.seq,
            room: RoomInfo::unknown(m.room),
            sender_type: "user".to_string(),
            sender: UserInfo::unknown(m.author),
            thread: m.thread,
            in_reply_to: m.parent,
            content: m.content,
            content_type: m.content_type,
            pinned: m.pinned,
            system_type: m.system_type,
            metadata: m.metadata,
            thinking_content: None,
            thinking_is_chunked: None,
            send_at: m.created_at,
            reactions: vec![],
        }
    }
}
