use chrono::Utc;
use uuid::Uuid;

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

use super::WsHandler;
use super::WsOutEvent;

impl WsHandler {
    pub(super) async fn reaction_add(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        message: Uuid,
        emoji: String,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        Self::ensure_message_in_room(bus, room, message).await?;
        if emoji.is_empty() || emoji.len() > 100 {
            return Err(ChannelError::Validation("invalid emoji".into()));
        }
        let seq = bus.inner.seq.seq(room).await?;
        let result = db::sqlx::query(
            "INSERT INTO room_reaction (message, \"user\", seq, reaction, created_at) \
             VALUES ($1, $2, $3, $4, now()) \
             ON CONFLICT DO NOTHING",
        )
        .bind(message)
        .bind(user_id)
        .bind(seq)
        .bind(&emoji)
        .execute(bus.inner.db.writer())
        .await?;
        if result.rows_affected() == 0 {
            return Ok(None);
        }
        let user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let rct_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let data = reaction::ReactionAddedService {
            id: Uuid::now_v7(),
            room: rct_room,
            message,
            user,
            emoji,
            created_at: Utc::now(),
        };
        bus.publish_room_event(room, "reaction.added", &data)
            .await?;
        Ok(Some(WsOutEvent::ReactionAdded {
            room: data.room.clone(),
            data,
        }))
    }

    pub(super) async fn reaction_remove(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        message: Uuid,
        emoji: String,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        Self::ensure_message_in_room(bus, room, message).await?;
        let result = db::sqlx::query(
            "DELETE FROM room_reaction WHERE message = $1 AND \"user\" = $2 AND reaction = $3",
        )
        .bind(message)
        .bind(user_id)
        .bind(&emoji)
        .execute(bus.inner.db.writer())
        .await?;
        if result.rows_affected() == 0 {
            return Ok(None);
        }
        let user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let rct_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let data = reaction::ReactionRemovedService {
            id: Uuid::now_v7(),
            room: rct_room,
            message,
            user,
            emoji,
            removed_at: Utc::now(),
        };
        bus.publish_room_event(room, "reaction.removed", &data)
            .await?;
        Ok(Some(WsOutEvent::ReactionRemoved {
            room: data.room.clone(),
            data,
        }))
    }
}
