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

use crate::event::{RoomInfo, UserInfo, member, presence};
use crate::{ChannelBus, ChannelResult};

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

impl WsHandler {
    pub(super) async fn dnd_update(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        do_not_disturb: Option<bool>,
        dnd_start_hour: Option<i16>,
        dnd_end_hour: Option<i16>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let do_not_disturb = do_not_disturb.unwrap_or(false);
        let start_hour = dnd_start_hour.map(|h| h as i32);
        let end_hour = dnd_end_hour.map(|h| h as i32);
        let key = format!("dnd:{}:{}", user_id, room);
        let dnd_data = serde_json::json!({
            "do_not_disturb": do_not_disturb,
            "dnd_start_hour": start_hour,
            "dnd_end_hour": end_hour,
        });
        bus.inner.cache.set(&key, &dnd_data).await?;
        let dnd_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let dnd_user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = member::DndUpdatedService {
            room: dnd_room,
            user: dnd_user,
            do_not_disturb,
            dnd_start_hour: start_hour,
            dnd_end_hour: end_hour,
        };
        bus.publish_room_event(room, "member.dnd_updated", &data)
            .await?;
        Ok(None)
    }

    pub(super) async fn presence_update(
        bus: &ChannelBus,
        user_id: Uuid,
        status: presence::UserPresenceStatus,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let pc_user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = presence::PresenceChangedService {
            user: pc_user,
            project: None,
            status,
            last_seen_at: Some(Utc::now()),
        };
        let rooms = crate::rooms::user_rooms(
            &bus.inner.db,
            &bus.inner.cache,
            &bus.inner.config,
            user_id,
        )
        .await?;
        for room in rooms {
            bus.publish_room_event(room, "presence.changed", &data)
                .await?;
        }
        Ok(Some(WsOutEvent::PresenceChanged { data }))
    }

    pub(super) async fn custom_status_update(
        bus: &ChannelBus,
        user_id: Uuid,
        emoji: Option<String>,
        text: Option<String>,
        expires_at: Option<chrono::DateTime<chrono::Utc>>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let cs_user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = presence::CustomStatusUpdatedService {
            user: cs_user,
            emoji,
            text,
            expires_at,
        };
        let rooms = crate::rooms::user_rooms(
            &bus.inner.db,
            &bus.inner.cache,
            &bus.inner.config,
            user_id,
        )
        .await?;
        for room in rooms {
            bus.publish_room_event(room, "custom_status.updated", &data)
                .await?;
        }
        Ok(Some(WsOutEvent::CustomStatusUpdated { data }))
    }
}
