use chrono::{DateTime, Utc};
use uuid::Uuid;

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

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

/// Helper struct for thread_list JOIN query result
#[derive(db::sqlx::FromRow)]
struct ThreadListRow {
    id: Uuid,
    room: Uuid,
    seq: i64,
    starter_message: Option<Uuid>,
    title: String,
    created_by: Uuid,
    archived: bool,
    locked: bool,
    last_message_at: Option<DateTime<Utc>>,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    archived_at: Option<DateTime<Utc>>,
    parent_seq: i64,
}

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

        // Join room_thread with room_message to get the parent message's seq
        let rows = db::sqlx::query_as::<_, ThreadListRow>(
            "SELECT rt.id, rt.room, rt.seq, rt.starter_message, rt.title, rt.created_by, \
             rt.archived, rt.locked, rt.last_message_at, rt.created_at, rt.updated_at, rt.archived_at, \
             COALESCE(rm.seq, 0) as parent_seq \
             FROM room_thread rt \
             LEFT JOIN room_message rm ON rm.id = rt.starter_message \
             WHERE rt.room = $1 ORDER BY rt.last_message_at DESC NULLS LAST",
        )
        .bind(room)
        .fetch_all(bus.inner.db.reader())
        .await?;

        let mut items = Vec::new();
        for row in rows {
            let tc_room = bus
                .lookup_room(row.room)
                .await
                .unwrap_or_else(|_| RoomInfo::unknown(row.room));
            let created_by = bus
                .lookup_user(row.created_by)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(row.created_by));
            // Get last message preview
            let preview: Option<(String,)> = db::sqlx::query_as(
                "SELECT content FROM room_message \
                 WHERE thread = $1 AND deleted_at IS NULL \
                 ORDER BY seq DESC LIMIT 1",
            )
            .bind(row.id)
            .fetch_optional(bus.inner.db.reader())
            .await?;
            items.push(thread::ThreadListItem {
                id: row.id,
                room: tc_room,
                seq: row.seq,
                parent_seq: row.parent_seq,
                title: row.title,
                created_by,
                archived: row.archived,
                locked: row.locked,
                last_message_at: row.last_message_at,
                last_message_preview: preview.map(|p| p.0),
                created_at: row.created_at,
            });
        }

        Ok(Some(WsOutEvent::ThreadList {
            data: thread::ThreadListService { threads: items },
        }))
    }

    pub(super) async fn thread_create(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        parent: i64,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        // Look up the message UUID from seq + room
        let parent_id: Option<(Uuid,)> = db::sqlx::query_as(
            "SELECT id FROM room_message WHERE room = $1 AND seq = $2 AND deleted_at IS NULL",
        )
        .bind(room)
        .bind(parent)
        .fetch_optional(bus.inner.db.reader())
        .await?;
        let parent_msg_id = parent_id.ok_or(ChannelError::RoomNotFound)?.0;
        let seq = bus.inner.seq.seq(room).await?;
        let row = db::sqlx::query_as::<_, model::channel::RoomThreadModel>(
            "INSERT INTO room_thread (room, seq, starter_message, title, created_by, created_at, updated_at) \
             VALUES ($1, $2, $3, '', $4, now(), now()) \
             RETURNING id, room, seq, starter_message, title, created_by, archived, locked, \
             last_message_at, created_at, updated_at, archived_at",
        )
        .bind(room)
        .bind(seq)
        .bind(parent_msg_id)  // UUID of the starter message
        .bind(user_id)
        .fetch_one(bus.inner.db.writer())
        .await?;
        let tc_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let created_by = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = thread::ThreadCreatedService {
            id: row.id,
            room: tc_room,
            parent,
            created_by,
            participants: serde_json::Value::Null,
            created_at: row.created_at,
        };
        bus.publish_room_event(room, "thread.created", &data)
            .await?;
        Ok(Some(WsOutEvent::ThreadCreated {
            room: data.room.clone(),
            data,
        }))
    }

    pub(super) async fn thread_resolve(
        bus: &ChannelBus,
        user_id: Uuid,
        thread_id: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let existing: (Uuid,) =
            db::sqlx::query_as("SELECT room FROM room_thread WHERE id = $1")
                .bind(thread_id)
                .fetch_optional(bus.inner.db.reader())
                .await?
                .ok_or(ChannelError::RoomNotFound)?;
        Self::ensure_room_access(bus, user_id, existing.0).await?;
        let row = db::sqlx::query_as::<_, model::channel::RoomThreadModel>(
            "UPDATE room_thread SET locked = true, updated_at = now() \
             WHERE id = $1 \
             RETURNING id, room, seq, starter_message, title, created_by, archived, locked, \
             last_message_at, created_at, updated_at, archived_at",
        )
        .bind(thread_id)
        .fetch_one(bus.inner.db.writer())
        .await?;
        let tr_room = bus
            .lookup_room(row.room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(row.room));
        let resolved_by = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = thread::ThreadResolvedService {
            id: row.id,
            room: tr_room,
            resolved_by,
            resolved_at: Utc::now(),
        };
        bus.publish_room_event(row.room, "thread.resolved", &data)
            .await?;
        Ok(Some(WsOutEvent::ThreadResolved {
            room: data.room.clone(),
            data,
        }))
    }

    pub(super) async fn thread_archive(
        bus: &ChannelBus,
        user_id: Uuid,
        thread_id: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let existing: (Uuid,) =
            db::sqlx::query_as("SELECT room FROM room_thread WHERE id = $1")
                .bind(thread_id)
                .fetch_optional(bus.inner.db.reader())
                .await?
                .ok_or(ChannelError::RoomNotFound)?;
        Self::ensure_room_access(bus, user_id, existing.0).await?;
        let row = db::sqlx::query_as::<_, model::channel::RoomThreadModel>(
            "UPDATE room_thread SET archived = true, archived_at = now(), updated_at = now() \
             WHERE id = $1 \
             RETURNING id, room, seq, starter_message, title, created_by, archived, locked, \
             last_message_at, created_at, updated_at, archived_at",
        )
        .bind(thread_id)
        .fetch_one(bus.inner.db.writer())
        .await?;
        let ta_room = bus
            .lookup_room(row.room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(row.room));
        let archived_by = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = thread::ThreadArchivedService {
            id: row.id,
            room: ta_room,
            archived_by,
            archived_at: Utc::now(),
        };
        bus.publish_room_event(row.room, "thread.archived", &data)
            .await?;
        Ok(Some(WsOutEvent::ThreadArchived {
            room: data.room.clone(),
            data,
        }))
    }
}
