use crate::error::RoomError;
use crate::service::RoomService;
use crate::ws_context::WsUserContext;
use chrono::Utc;
use models::rooms::room_message_reaction;
use models::users::user as user_model;
use queue::ReactionGroup;
use sea_orm::*;
use uuid::Uuid;

#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct ReactionGroupResponse {
    pub emoji: String,
    pub count: i32,
    pub reacted_by_me: bool,
    pub users: Vec<String>,
}

#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct MessageReactionsResponse {
    pub message_id: Uuid,
    pub reactions: Vec<ReactionGroupResponse>,
}

#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct MessageSearchResponse {
    pub messages: Vec<super::RoomMessageResponse>,
    pub total: i64,
}

impl RoomService {
    pub async fn message_reaction_add(
        &self,
        message_id: Uuid,
        emoji: String,
        ctx: &WsUserContext,
    ) -> Result<MessageReactionsResponse, RoomError> {
        let user_id = ctx.user_id;

        let message = self.find_message_or_404(message_id).await?;
        self.require_room_member(message.room, user_id).await?;

        Self::validate_emoji(&emoji)?;

        let now = Utc::now();

        // Use a transaction to atomically check-and-insert, preventing race conditions
        let txn = self.db.begin().await?;

        let existing = room_message_reaction::Entity::find()
            .filter(room_message_reaction::Column::Message.eq(message_id))
            .filter(room_message_reaction::Column::User.eq(user_id))
            .filter(room_message_reaction::Column::Emoji.eq(&emoji))
            .one(&txn)
            .await?;

        if existing.is_some() {
            txn.commit().await?;
            return self.get_message_reactions(message_id, Some(user_id)).await;
        }

        let reaction = room_message_reaction::ActiveModel {
            id: Set(Uuid::now_v7()),
            room: Set(message.room),
            message: Set(message_id),
            user: Set(user_id),
            emoji: Set(emoji.clone()),
            created_at: Set(now),
        };

        room_message_reaction::Entity::insert(reaction)
            .exec(&txn)
            .await?;

        txn.commit().await?;

        // Only publish if we actually inserted a new reaction
        let reactions = self
            .get_message_reactions(message_id, Some(user_id))
            .await?;
        let reaction_groups = reactions
            .reactions
            .into_iter()
            .map(|g| ReactionGroup {
                emoji: g.emoji,
                count: g.count as i32,
                reacted_by_me: g.reacted_by_me,
                users: g.users.into_iter().map(|u| u.to_string()).collect(),
            })
            .collect();
        self.queue
            .publish_reaction_event(message.room, message_id, reaction_groups)
            .await;

        self.get_message_reactions(message_id, Some(user_id)).await
    }

    pub async fn message_reaction_remove(
        &self,
        message_id: Uuid,
        emoji: String,
        ctx: &WsUserContext,
    ) -> Result<MessageReactionsResponse, RoomError> {
        let user_id = ctx.user_id;

        let message = self.find_message_or_404(message_id).await?;
        self.require_room_member(message.room, user_id).await?;

        room_message_reaction::Entity::delete_many()
            .filter(room_message_reaction::Column::Message.eq(message_id))
            .filter(room_message_reaction::Column::User.eq(user_id))
            .filter(room_message_reaction::Column::Emoji.eq(emoji))
            .exec(&self.db)
            .await?;

        let reactions = self
            .get_message_reactions(message_id, Some(user_id))
            .await?;
        let reaction_groups = reactions
            .reactions
            .into_iter()
            .map(|g| ReactionGroup {
                emoji: g.emoji,
                count: g.count as i32,
                reacted_by_me: g.reacted_by_me,
                users: g.users.into_iter().map(|u| u.to_string()).collect(),
            })
            .collect();
        self.queue
            .publish_reaction_event(message.room, message_id, reaction_groups)
            .await;

        self.get_message_reactions(message_id, Some(user_id)).await
    }

    pub async fn message_reactions_get(
        &self,
        message_id: Uuid,
        ctx: &WsUserContext,
    ) -> Result<MessageReactionsResponse, RoomError> {
        let user_id = ctx.user_id;

        let message = self.find_message_or_404(message_id).await?;
        self.require_room_member(message.room, user_id).await?;

        self.get_message_reactions(message_id, Some(user_id)).await
    }

    pub async fn message_reactions_batch(
        &self,
        room_id: Uuid,
        message_ids: Vec<Uuid>,
        ctx: &WsUserContext,
    ) -> Result<Vec<MessageReactionsResponse>, RoomError> {
        let user_id = ctx.user_id;
        self.require_room_member(room_id, user_id).await?;

        let mut results = Vec::with_capacity(message_ids.len());
        for msg_id in message_ids {
            let reactions = self.get_message_reactions(msg_id, Some(user_id)).await?;
            results.push(reactions);
        }
        Ok(results)
    }

    pub async fn message_search(
        &self,
        room_id: Uuid,
        query: &str,
        limit: Option<u64>,
        offset: Option<u64>,
        ctx: &WsUserContext,
    ) -> Result<MessageSearchResponse, RoomError> {
        let user_id = ctx.user_id;
        self.require_room_member(room_id, user_id).await?;

        if query.trim().is_empty() {
            return Ok(MessageSearchResponse {
                messages: Vec::new(),
                total: 0,
            });
        }

        let limit = limit.unwrap_or(20);
        let offset = offset.unwrap_or(0);

        let search_pattern = format!("%{}%", query);

        let query_builder = models::rooms::room_message::Entity::find()
            .filter(models::rooms::room_message::Column::Room.eq(room_id))
            .filter(models::rooms::room_message::Column::Content.like(&search_pattern))
            .filter(models::rooms::room_message::Column::Revoked.is_null());

        let total = query_builder.clone().count(&self.db).await? as i64;

        let messages = query_builder
            .order_by_desc(models::rooms::room_message::Column::SendAt)
            .limit(limit)
            .offset(offset)
            .all(&self.db)
            .await?;

        let response_messages = self.build_messages_with_display_names(messages).await;

        Ok(MessageSearchResponse {
            messages: response_messages,
            total,
        })
    }

    pub(crate) async fn find_message_or_404(
        &self,
        message_id: Uuid,
    ) -> Result<models::rooms::room_message::Model, RoomError> {
        models::rooms::room_message::Entity::find_by_id(message_id)
            .one(&self.db)
            .await?
            .ok_or_else(|| RoomError::NotFound("Message not found".to_string()))
    }

    pub(crate) fn validate_emoji(emoji: &str) -> Result<(), RoomError> {
        if emoji.is_empty() || emoji.len() > 50 {
            return Err(RoomError::BadRequest("Invalid emoji format".to_string()));
        }
        Ok(())
    }

    pub(crate) async fn get_message_reactions(
        &self,
        message_id: Uuid,
        current_user_id: Option<Uuid>,
    ) -> Result<MessageReactionsResponse, RoomError> {
        let reactions = room_message_reaction::Entity::find()
            .filter(room_message_reaction::Column::Message.eq(message_id))
            .all(&self.db)
            .await?;

        let reaction_groups = self.build_reaction_groups(reactions, current_user_id);

        Ok(MessageReactionsResponse {
            message_id,
            reactions: reaction_groups,
        })
    }

    pub(crate) fn build_reaction_groups(
        &self,
        reactions: Vec<room_message_reaction::Model>,
        current_user_id: Option<Uuid>,
    ) -> Vec<ReactionGroupResponse> {
        let mut grouped: std::collections::HashMap<String, Vec<&room_message_reaction::Model>> =
            std::collections::HashMap::new();

        for r in &reactions {
            grouped.entry(r.emoji.clone()).or_default().push(r);
        }

        grouped
            .into_iter()
            .map(|(emoji, user_reactions)| {
                let count = user_reactions.len() as i32;
                let reacted_by_me = current_user_id
                    .map(|uid| user_reactions.iter().any(|r| r.user == uid))
                    .unwrap_or(false);
                let users = user_reactions
                    .iter()
                    .take(3)
                    .map(|r| r.user.to_string())
                    .collect();

                ReactionGroupResponse {
                    emoji,
                    count,
                    reacted_by_me,
                    users,
                }
            })
            .collect()
    }

    pub(crate) async fn build_messages_with_display_names(
        &self,
        messages: Vec<models::rooms::room_message::Model>,
    ) -> Vec<super::RoomMessageResponse> {
        let user_ids: Vec<Uuid> = messages
            .iter()
            .filter(|m| m.sender_type.to_string() == "member")
            .filter_map(|m| m.sender_id)
            .collect();

        let users: std::collections::HashMap<Uuid, String> = if !user_ids.is_empty() {
            user_model::Entity::find()
                .filter(user_model::Column::Uid.is_in(user_ids))
                .all(&self.db)
                .await
                .unwrap_or_default()
                .into_iter()
                .map(|u| (u.uid, u.display_name.unwrap_or(u.username)))
                .collect()
        } else {
            std::collections::HashMap::new()
        };

        messages
            .into_iter()
            .map(|msg| {
                let sender_type = msg.sender_type.to_string();
                let display_name = match sender_type.as_str() {
                    "member" => msg.sender_id.and_then(|id| users.get(&id).cloned()),
                    _ => None,
                };

                super::RoomMessageResponse {
                    id: msg.id,
                    seq: msg.seq,
                    room: msg.room,
                    sender_type,
                    sender_id: msg.sender_id,
                    display_name,
                    thread: msg.thread,
                    in_reply_to: msg.in_reply_to,
                    content: msg.content,
                    content_type: msg.content_type.to_string(),
                    thinking_content: msg.thinking_content,
                    edited_at: msg.edited_at,
                    send_at: msg.send_at,
                    revoked: msg.revoked,
                    revoked_by: msg.revoked_by,
                    highlighted_content: None,
                    attachment_ids: Vec::new(),
                }
            })
            .collect()
    }
}
