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

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

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

impl WsHandler {
    pub(super) async fn subscribe(
        bus: &ChannelBus,
        user_id: Uuid,
        _room: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        bus.refresh_user(user_id).await?;
        Ok(None)
    }

    pub(super) async fn unsubscribe(
        _bus: &ChannelBus,
        _user_id: Uuid,
        _room: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Ok(None)
    }

    pub(super) async fn typing(
        bus: &ChannelBus,
        room: Uuid,
        user_id: Uuid,
        action: &str,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;

        let key = (room, user_id);

        if action == "start" {
            let ty_room = bus
                .lookup_room(room)
                .await
                .unwrap_or_else(|_| RoomInfo::unknown(room));
            let ty_user = bus
                .lookup_user(user_id)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(user_id));
            let already_typing = bus.inner.typing_states.contains_key(&key);
            if let Some((_, (_, _, old_cancel))) =
                bus.inner.typing_states.remove(&key)
            {
                old_cancel.cancel();
            }

            let cancel = tokio_util::sync::CancellationToken::new();
            let cancel_clone = cancel.clone();
            let bus_clone = bus.clone();
            let user_clone = ty_user.clone();
            let room_clone = ty_room.clone();
            bus.inner
                .typing_states
                .insert(key, (ty_user.clone(), ty_room.clone(), cancel));
            tokio::spawn(async move {
                tokio::time::sleep(std::time::Duration::from_secs(10)).await;
                if cancel_clone.is_cancelled() {
                    return;
                }
                bus_clone
                    .inner
                    .typing_states
                    .remove(&(room_clone.id, user_clone.id));
                let room_id = room_clone.id;
                let stop_data = member::TypingStopService {
                    room: room_clone,
                    user: user_clone,
                    sender_type: "user".to_string(),
                    stopped_at: Utc::now(),
                };
                let _ = bus_clone
                    .publish_room_event(room_id, "typing.stop", &stop_data)
                    .await;
            });
            if !already_typing {
                let data = member::TypingStartService {
                    room: ty_room,
                    user: ty_user,
                    sender_type: "user".to_string(),
                    started_at: Utc::now(),
                };
                bus.publish_room_event(room, "typing.start", &data).await?;
                return Ok(Some(WsOutEvent::TypingStart {
                    room: data.room.clone(),
                    data,
                }));
            }
            Ok(None)
        } else {
            if let Some((_, (_, _, cancel))) =
                bus.inner.typing_states.remove(&key)
            {
                cancel.cancel();
            }

            let ty_room = bus
                .lookup_room(room)
                .await
                .unwrap_or_else(|_| RoomInfo::unknown(room));
            let ty_user = bus
                .lookup_user(user_id)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(user_id));

            let data = member::TypingStopService {
                room: ty_room,
                user: ty_user,
                sender_type: "user".to_string(),
                stopped_at: Utc::now(),
            };
            bus.publish_room_event(room, "typing.stop", &data).await?;
            Ok(Some(WsOutEvent::TypingStop {
                room: data.room.clone(),
                data,
            }))
        }
    }

    pub(super) async fn read_receipt(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        last_read_seq: i64,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        bus.inner
            .reconnect
            .save_client_state(user_id, room, last_read_seq)
            .await?;
        db::sqlx::query(
            "INSERT INTO user_room_state (\"user\", room, last_read_seq, last_read_at, updated_at) \
             VALUES ($1, $2, $3, now(), now()) \
             ON CONFLICT (\"user\", room) DO UPDATE \
             SET last_read_seq = GREATEST(user_room_state.last_read_seq, $3), \
                 last_read_at = now(), updated_at = now()",
        )
        .bind(user_id)
        .bind(room)
        .bind(last_read_seq)
        .execute(bus.inner.db.writer())
        .await?;
        let rr_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let rr_user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = member::ReadReceiptService {
            room: rr_room.clone(),
            user: rr_user,
            last_read_seq,
            updated_at: Utc::now(),
        };
        bus.publish_room_event(room, "member.read_receipt", &data)
            .await?;
        Ok(Some(WsOutEvent::ReadReceipt {
            room: rr_room,
            data,
        }))
    }

    pub(super) async fn csrf_token(
        bus: &ChannelBus,
        user_id: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let token = bus.inner.csrf.generate_token(user_id).await?;
        Ok(Some(WsOutEvent::CsrfToken { token }))
    }
}
