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

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

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

impl WsHandler {
    pub(super) async fn conversation_pin(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        pin: bool,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let now = Utc::now();
        db::sqlx::query(
            "INSERT INTO user_room_state (\"user\", room, is_pinned, updated_at) \
             VALUES ($1, $2, $3, $4) \
             ON CONFLICT (\"user\", room) DO UPDATE \
             SET is_pinned = $3, updated_at = $4",
        )
        .bind(user_id)
        .bind(room)
        .bind(pin)
        .bind(now)
        .execute(bus.inner.db.writer())
        .await?;

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

        if pin {
            let data = conversation::ConversationPinnedService {
                user: user_info,
                room: room_info.clone(),
                pinned_at: now,
            };
            bus.emit_to_user(user_id, "conversation.pinned", &data)
                .await?;
            Ok(Some(WsOutEvent::ConversationPinned {
                room: room_info,
                data,
            }))
        } else {
            let data = conversation::ConversationUnpinnedService {
                user: user_info,
                room: room_info.clone(),
                unpinned_at: now,
            };
            bus.emit_to_user(user_id, "conversation.unpinned", &data)
                .await?;
            Ok(Some(WsOutEvent::ConversationUnpinned {
                room: room_info,
                data,
            }))
        }
    }
    pub(super) async fn conversation_mute(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        mute: bool,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let now = Utc::now();

        db::sqlx::query(
            "INSERT INTO user_room_state (\"user\", room, is_muted, updated_at) \
             VALUES ($1, $2, $3, $4) \
             ON CONFLICT (\"user\", room) DO UPDATE \
             SET is_muted = $3, updated_at = $4",
        )
        .bind(user_id)
        .bind(room)
        .bind(mute)
        .bind(now)
        .execute(bus.inner.db.writer())
        .await?;

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

        if mute {
            let data = conversation::ConversationMutedService {
                user: user_info,
                room: room_info.clone(),
                muted_at: now,
            };
            bus.emit_to_user(user_id, "conversation.muted", &data)
                .await?;
            Ok(Some(WsOutEvent::ConversationMuted {
                room: room_info,
                data,
            }))
        } else {
            let data = conversation::ConversationUnmutedService {
                user: user_info,
                room: room_info.clone(),
                unmuted_at: now,
            };
            bus.emit_to_user(user_id, "conversation.unmuted", &data)
                .await?;
            Ok(Some(WsOutEvent::ConversationUnmuted {
                room: room_info,
                data,
            }))
        }
    }
    pub(super) async fn conversation_notify_level(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        notify_level: String,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let valid =
            matches!(notify_level.as_str(), "all" | "mentions" | "none");
        if !valid {
            return Err(crate::ChannelError::Internal(
                "notify_level must be 'all', 'mentions', or 'none'".to_string(),
            ));
        }

        let now = Utc::now();
        let old_level: Option<(String,)> = db::sqlx::query_as(
            "SELECT notify_level FROM user_room_state \
             WHERE \"user\" = $1 AND room = $2",
        )
        .bind(user_id)
        .bind(room)
        .fetch_optional(bus.inner.db.reader())
        .await?;
        let old = old_level.map(|r| r.0).unwrap_or_else(|| "all".to_string());

        db::sqlx::query(
            "INSERT INTO user_room_state (\"user\", room, notify_level, updated_at) \
             VALUES ($1, $2, $3, $4) \
             ON CONFLICT (\"user\", room) DO UPDATE \
             SET notify_level = $3, updated_at = $4",
        )
        .bind(user_id)
        .bind(room)
        .bind(&notify_level)
        .bind(now)
        .execute(bus.inner.db.writer())
        .await?;

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

        let data = conversation::ConversationNotifyLevelChangedService {
            user: user_info,
            room: room_info.clone(),
            old_level: old,
            new_level: notify_level,
            updated_at: now,
        };
        bus.emit_to_user(user_id, "conversation.notify_level_changed", &data)
            .await?;
        Ok(None)
    }
    pub(super) async fn conversation_list(
        bus: &ChannelBus,
        user_id: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let rooms = crate::rooms::user_rooms(
            &bus.inner.db,
            &bus.inner.cache,
            &bus.inner.config,
            user_id,
        )
        .await?;

        if rooms.is_empty() {
            return Ok(Some(WsOutEvent::ConversationList { data: vec![] }));
        }
        let rows = db::sqlx::query_as::<_, (
            Uuid,           // room id
            String,         // room name
            String,         // room type
            bool,           // is_pinned
            bool,           // is_muted
            String,         // notify_level
            i64,            // last_read_seq
            i64,            // max seq from room_message
        )>(
            "SELECT r.id, r.name, r.room_type, \
                    COALESCE(s.is_pinned, false), \
                    COALESCE(s.is_muted, false), \
                    COALESCE(s.notify_level, 'all'), \
                    COALESCE(s.last_read_seq, 0), \
                    COALESCE((SELECT MAX(seq) FROM room_message \
                              WHERE room = r.id AND deleted_at IS NULL), 0) \
             FROM room r \
             LEFT JOIN user_room_state s ON s.room = r.id AND s.\"user\" = $1 \
             WHERE r.id = ANY($2) AND r.deleted_at IS NULL AND r.is_archived = false \
             ORDER BY COALESCE(s.is_pinned, false) DESC, r.name",
        )
        .bind(user_id)
        .bind(&rooms)
        .fetch_all(bus.inner.db.reader())
        .await?;

        let summaries: Vec<conversation::ConversationSummary> = rows
            .into_iter()
            .map(
                |(
                    id,
                    name,
                    room_type,
                    is_pinned,
                    is_muted,
                    notify_level,
                    last_read_seq,
                    max_seq,
                )| {
                    let unread = (max_seq - last_read_seq).max(0);
                    conversation::ConversationSummary {
                        room: id,
                        room_name: name,
                        room_type,
                        is_pinned,
                        is_muted,
                        notify_level,
                        last_read_seq,
                        max_seq,
                        unread_count: unread,
                        last_read_at: None,
                    }
                },
            )
            .collect();

        Ok(Some(WsOutEvent::ConversationList { data: summaries }))
    }
}
