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

use crate::event::{UserInfo, WorkspaceInfo, category};
use crate::{ChannelBus, ChannelError, ChannelResult};

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

impl WsHandler {
    pub(super) async fn category_create(
        bus: &ChannelBus,
        user_id: Uuid,
        workspace: Uuid,
        name: String,
        position: Option<i32>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        if name.is_empty() || name.len() > MAX_CATEGORY_NAME_LEN {
            return Err(ChannelError::Validation(
                "invalid category name".into(),
            ));
        }
        Self::ensure_workspace_member(bus, user_id, workspace).await?;
        let row = db::sqlx::query_as::<_, model::channel::RoomCategoryModel>(
            "INSERT INTO room_category (wk, name, position, created_at, updated_at) \
             VALUES ($1, $2, $3, now(), now()) \
             RETURNING id, wk, name, position, collapsed, created_at, updated_at",
        )
        .bind(workspace)
        .bind(&name)
        .bind(position.unwrap_or(0))
        .fetch_one(bus.inner.db.writer())
        .await?;
        let cc_workspace = bus
            .lookup_workspace(workspace)
            .await
            .unwrap_or_else(|_| WorkspaceInfo::unknown(workspace));
        let cc_user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = category::CategoryCreatedService {
            id: row.id,
            project: cc_workspace,
            name: row.name,
            position: row.position,
            created_by: cc_user,
            created_at: row.created_at,
        };
        bus.workspace_changed(workspace).await?;
        Ok(Some(WsOutEvent::CategoryCreated {
            workspace: data.project.clone(),
            data,
        }))
    }

    pub(super) async fn category_update(
        bus: &ChannelBus,
        user_id: Uuid,
        id: Uuid,
        name: Option<String>,
        position: Option<i32>,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let old = db::sqlx::query_as::<_, model::channel::RoomCategoryModel>(
            "SELECT id, wk, name, position, collapsed, created_at, updated_at \
             FROM room_category WHERE id = $1",
        )
        .bind(id)
        .fetch_one(bus.inner.db.reader())
        .await?;
        Self::ensure_workspace_member(bus, user_id, old.wk).await?;
        let new_name = name.unwrap_or(old.name.clone());
        let new_position = position.unwrap_or(old.position);
        db::sqlx::query(
            "UPDATE room_category SET name = $2, position = $3, updated_at = now() WHERE id = $1",
        )
        .bind(id)
        .bind(&new_name)
        .bind(new_position)
        .execute(bus.inner.db.writer())
        .await?;
        let cu_workspace = bus
            .lookup_workspace(old.wk)
            .await
            .unwrap_or_else(|_| WorkspaceInfo::unknown(old.wk));
        let cu_user = bus
            .lookup_user(user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(user_id));
        let data = category::CategoryUpdatedService {
            id,
            project: cu_workspace,
            name: Some(new_name),
            position: Some(new_position),
            updated_by: cu_user,
            updated_at: Utc::now(),
        };
        bus.workspace_changed(old.wk).await?;
        Ok(Some(WsOutEvent::CategoryUpdated {
            workspace: data.project.clone(),
            data,
        }))
    }

    pub(super) async fn category_delete(
        bus: &ChannelBus,
        _user_id: Uuid,
        id: Uuid,
    ) -> ChannelResult<Option<WsOutEvent>> {
        let existing = db::sqlx::query_as::<_, model::channel::RoomCategoryModel>(
            "SELECT id, wk, name, position, collapsed, created_at, updated_at \
             FROM room_category WHERE id = $1",
        )
        .bind(id)
        .fetch_one(bus.inner.db.reader())
        .await?;
        Self::ensure_workspace_member(bus, _user_id, existing.wk).await?;
        let row = db::sqlx::query_as::<_, model::channel::RoomCategoryModel>(
            "DELETE FROM room_category WHERE id = $1 \
             RETURNING id, wk, name, position, collapsed, created_at, updated_at",
        )
        .bind(id)
        .fetch_one(bus.inner.db.writer())
        .await?;
        let cd_workspace = bus
            .lookup_workspace(row.wk)
            .await
            .unwrap_or_else(|_| WorkspaceInfo::unknown(row.wk));
        let cd_user = bus
            .lookup_user(_user_id)
            .await
            .unwrap_or_else(|_| UserInfo::unknown(_user_id));
        let data = category::CategoryDeletedService {
            id: row.id,
            project: cd_workspace.clone(),
            deleted_by: cd_user,
            deleted_at: Utc::now(),
        };
        bus.workspace_changed(row.wk).await?;
        Ok(Some(WsOutEvent::CategoryDeleted {
            workspace: cd_workspace,
            data,
        }))
    }
}
