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

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

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

impl WsHandler {
    pub(super) async fn room_get(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let row = db::sqlx::query_as::<_, model::channel::ChannelModel>(
            "SELECT id, wk, parent, name, topic, room_type, position, \
             is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at \
             FROM room WHERE id = $1 AND deleted_at IS NULL",
        )
        .bind(room)
        .fetch_one(bus.inner.db.reader())
        .await?;
        Ok(Some(WsOutEvent::Response {
            request_id: Uuid::nil(),
            data: serde_json::json!({
                "id": row.id,
                "wk": row.wk,
                "name": row.name,
                "topic": row.topic,
                "room_type": row.channel_type,
                "is_private": row.is_private,
                "is_archived": row.is_archived,
                "ai_enabled": row.ai_enabled,
                "parent": row.parent,
                "created_by": row.created_by,
                "created_at": row.created_at,
            }),
        }))
    }

    pub(super) async fn room_create(
        bus: &ChannelBus,
        user_id: Uuid,
        workspace: Uuid,
        room_name: String,
        public: bool,
        category: Option<Uuid>,
        ai_enabled: Option<bool>,
        channel_type: Option<String>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        if room_name.is_empty() || room_name.len() > MAX_ROOM_NAME_LEN {
            return Err(ChannelError::Validation("invalid room name".into()));
        }
        Self::ensure_workspace_member(bus, user_id, workspace).await?;
        let is_private = !public;
        let ai = ai_enabled.unwrap_or(false);
        let ctype = channel_type.unwrap_or_else(|| "channel".to_string());
        let row = db::sqlx::query_as::<_, model::channel::ChannelModel>(
            "INSERT INTO room (wk, parent, name, room_type, is_private, ai_enabled, created_by, created_at, updated_at) \
             VALUES ($1, $2, $3, $4, $5, $6, $7, now(), now()) \
             RETURNING id, wk, parent, name, topic, room_type, position, \
             is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at",
        )
        .bind(workspace)
        .bind(category)
        .bind(&room_name)
        .bind(&ctype)
        .bind(is_private)
        .bind(ai)
        .bind(user_id)
        .fetch_one(bus.inner.db.writer())
        .await?;
        db::sqlx::query(
            "INSERT INTO room_permission_overwrite \
             (room, target_type, target_id, allow_permissions, deny_permissions, created_at, updated_at) \
             VALUES ($1, 'user', $2, '', '', now(), now())",
        )
        .bind(row.id)
        .bind(user_id)
        .execute(bus.inner.db.writer())
        .await?;
        let data = rooms::RoomCreatedService {
            room: RoomInfo::from_model(&row),
            workspace: bus
                .lookup_workspace(workspace)
                .await
                .unwrap_or_else(|_| WorkspaceInfo::unknown(workspace)),
            public,
            category,
            created_by: bus
                .lookup_user(user_id)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(user_id)),
            created_at: row.created_at,
        };
        bus.publish_room_event(row.id, "room.created", &data)
            .await?;
        bus.room_changed(row.id).await?;
        Ok(Some(WsOutEvent::RoomCreated {
            room: data.room.clone(),
            data,
        }))
    }

    pub(super) async fn room_update(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        room_name: Option<String>,
        public: Option<bool>,
        category: Option<Uuid>,
        ai_enabled: Option<bool>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let old = db::sqlx::query_as::<_, model::channel::ChannelModel>(
            "SELECT id, wk, parent, name, topic, room_type, position, \
             is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at \
             FROM room WHERE id = $1 AND deleted_at IS NULL",
        )
        .bind(room)
        .fetch_one(bus.inner.db.reader())
        .await?;
        let new_name = room_name.unwrap_or(old.name.clone());
        let new_private = public.map(|p| !p).unwrap_or(old.is_private);
        let new_category = category.or(old.parent);
        let new_ai = ai_enabled.unwrap_or(old.ai_enabled);
        let row = db::sqlx::query_as::<_, model::channel::ChannelModel>(
            "UPDATE room SET name = $2, is_private = $3, parent = $4, ai_enabled = $5, updated_at = now() \
             WHERE id = $1 AND deleted_at IS NULL \
             RETURNING id, wk, parent, name, topic, room_type, position, \
             is_private, is_archived, ai_enabled, created_by, created_at, updated_at, deleted_at",
        )
        .bind(room)
        .bind(&new_name)
        .bind(new_private)
        .bind(new_category)
        .bind(new_ai)
        .fetch_one(bus.inner.db.writer())
        .await?;
        let mut renamed = false;
        if new_name != old.name {
            let data = rooms::RoomRenamedService {
                room: RoomInfo::from_model(&row),
                workspace: bus
                    .lookup_workspace(row.wk)
                    .await
                    .unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
                old_name: old.name.clone(),
                new_name: new_name,
                renamed_by: bus
                    .lookup_user(user_id)
                    .await
                    .unwrap_or_else(|_| UserInfo::unknown(user_id)),
                renamed_at: Utc::now(),
            };
            bus.publish_room_event(room, "room.renamed", &data).await?;
            renamed = true;
        }
        if new_private != old.is_private
            || new_category != old.parent
            || new_ai != old.ai_enabled
        {
            let data = rooms::RoomSettingsUpdatedService {
                room: RoomInfo::from_model(&row),
                workspace: bus
                    .lookup_workspace(row.wk)
                    .await
                    .unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
                slowmode_seconds: None,
                nsfw: false,
                default_auto_archive_duration: None,
                updated_by: bus
                    .lookup_user(user_id)
                    .await
                    .unwrap_or_else(|_| UserInfo::unknown(user_id)),
                updated_at: Utc::now(),
            };
            bus.publish_room_event(room, "room.settings_updated", &data)
                .await?;
        }
        bus.room_changed(room).await?;
        if renamed {
            return Ok(Some(WsOutEvent::RoomRenamed {
                room: RoomInfo::from_model(&row),
                data: rooms::RoomRenamedService {
                    room: RoomInfo::from_model(&row),
                    workspace: bus
                        .lookup_workspace(row.wk)
                        .await
                        .unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
                    old_name: old.name,
                    new_name: row.name,
                    renamed_by: bus
                        .lookup_user(user_id)
                        .await
                        .unwrap_or_else(|_| UserInfo::unknown(user_id)),
                    renamed_at: Utc::now(),
                },
            }));
        }
        Ok(Some(WsOutEvent::Response {
            request_id: Uuid::nil(),
            data: serde_json::json!({
                "id": row.id,
                "name": row.name,
                "is_private": row.is_private,
                "ai_enabled": row.ai_enabled,
                "parent": row.parent,
            }),
        }))
    }

    pub(super) async fn room_delete(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        let old = db::sqlx::query_as::<_, model::channel::ChannelModel>(
            "SELECT id, wk, parent, name, topic, room_type, position, \
             is_private, is_archived, created_by, created_at, updated_at, deleted_at \
             FROM room WHERE id = $1 AND deleted_at IS NULL",
        )
        .bind(room)
        .fetch_one(bus.inner.db.reader())
        .await?;
        if old.created_by != user_id {
            return Err(ChannelError::AccessDenied);
        }
        let row = db::sqlx::query_as::<_, model::channel::ChannelModel>(
            "UPDATE room SET deleted_at = now(), updated_at = now() \
             WHERE id = $1 AND deleted_at IS NULL \
             RETURNING id, wk, parent, name, topic, room_type, position, \
             is_private, is_archived, created_by, created_at, updated_at, deleted_at",
        )
        .bind(room)
        .fetch_one(bus.inner.db.writer())
        .await?;
        let data = rooms::RoomDeletedService {
            room: RoomInfo::from_model(&row),
            workspace: bus
                .lookup_workspace(row.wk)
                .await
                .unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk)),
            deleted_by: bus
                .lookup_user(user_id)
                .await
                .unwrap_or_else(|_| UserInfo::unknown(user_id)),
            deleted_at: Utc::now(),
        };
        bus.publish_room_event(room, "room.deleted", &data).await?;
        bus.room_changed(room).await?;
        Ok(Some(WsOutEvent::RoomDeleted {
            room: data.room.clone(),
            data,
        }))
    }

    pub(super) async fn access_grant(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        target_user: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        db::sqlx::query(
            "INSERT INTO room_permission_overwrite \
             (room, target_type, target_id, allow_permissions, deny_permissions, created_at, updated_at) \
             SELECT $1, 'user', $2, '', '', now(), now() \
             WHERE NOT EXISTS (SELECT 1 FROM room_permission_overwrite WHERE room = $1 AND target_type = 'user' AND target_id = $2)",
        )
        .bind(room)
        .bind(target_user)
        .execute(bus.inner.db.writer())
        .await?;
        let mj_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let mj_user = bus
            .lookup_user(target_user)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(target_user));
        let data = member::MemberJoinedService {
            room: mj_room,
            user: mj_user,
            project_role: None,
            joined_at: Utc::now(),
        };
        bus.publish_room_event(room, "member.joined", &data).await?;
        bus.room_changed(room).await?;
        Ok(Some(WsOutEvent::MemberJoined {
            room: data.room.clone(),
            data,
        }))
    }

    pub(super) async fn access_revoke(
        bus: &ChannelBus,
        user_id: Uuid,
        room: Uuid,
        target_user: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        Self::ensure_room_access(bus, user_id, room).await?;
        db::sqlx::query(
            "DELETE FROM room_permission_overwrite \
             WHERE room = $1 AND target_type = 'user' AND target_id = $2",
        )
        .bind(room)
        .bind(target_user)
        .execute(bus.inner.db.writer())
        .await?;
        let mr_room = bus
            .lookup_room(room)
            .await
            .unwrap_or_else(|_| RoomInfo::unknown(room));
        let mr_target = bus
            .lookup_user(target_user)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(target_user));
        let mr_remover = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = member::MemberRemovedService {
            room: mr_room,
            user: mr_target,
            removed_by: mr_remover,
            removed_at: Utc::now(),
        };
        bus.publish_room_event(room, "member.removed", &data)
            .await?;
        bus.room_changed(room).await?;
        Ok(Some(WsOutEvent::MemberRemoved {
            room: data.room.clone(),
            data,
        }))
    }
}
