use crate::error::RoomError;
use crate::service::RoomService;
use crate::ws_context::WsUserContext;
use chrono::Utc;
use models::rooms::{room, room_category};
use queue::ProjectRoomEvent;
use sea_orm::prelude::Expr;
use sea_orm::*;
use uuid::Uuid;

impl RoomService {
    pub async fn room_category_list(
        &self,
        project_name: String,
        ctx: &WsUserContext,
    ) -> Result<Vec<super::RoomCategoryResponse>, RoomError> {
        let user_id = ctx.user_id;
        let project = self.utils_find_project_by_name(project_name).await?;
        self.check_project_access(project.id, user_id).await?;

        let models = room_category::Entity::find()
            .filter(room_category::Column::Project.eq(project.id))
            .order_by_asc(room_category::Column::Position)
            .all(&self.db)
            .await?;

        Ok(models
            .into_iter()
            .map(super::RoomCategoryResponse::from)
            .collect())
    }

    pub async fn room_category_create(
        &self,
        project_name: String,
        request: super::RoomCategoryCreateRequest,
        ctx: &WsUserContext,
    ) -> Result<super::RoomCategoryResponse, RoomError> {
        let user_id = ctx.user_id;
        let project = self.utils_find_project_by_name(project_name).await?;
        self.require_project_admin(project.id, user_id).await?;

        Self::validate_name(&request.name, super::MAX_CATEGORY_NAME_LEN)?;

        let position = if let Some(position) = request.position {
            position
        } else {
            let max_position: Option<Option<i32>> = room_category::Entity::find()
                .filter(room_category::Column::Project.eq(project.id))
                .select_only()
                .column_as(room_category::Column::Position.max(), "max_position")
                .into_tuple::<Option<i32>>()
                .one(&self.db)
                .await?;
            max_position.flatten().unwrap_or(0) + 1
        };

        let model = room_category::ActiveModel {
            id: Set(Uuid::now_v7()),
            project: Set(project.id),
            name: Set(request.name),
            position: Set(position),
            created_by: Set(user_id),
            created_at: Set(Utc::now()),
        }
        .insert(&self.db)
        .await?;

        let event = ProjectRoomEvent {
            event_type: super::RoomEventType::CategoryCreated.as_str().into(),
            project_id: project.id,
            room_id: None,
            category_id: Some(model.id),
            message_id: None,
            seq: None,
            timestamp: Utc::now(),
        };
        let _ = self
            .queue
            .publish_project_room_event(project.id, event)
            .await;

        Ok(super::RoomCategoryResponse::from(model))
    }

    pub async fn room_category_update(
        &self,
        category_id: Uuid,
        request: super::RoomCategoryUpdateRequest,
        ctx: &WsUserContext,
    ) -> Result<super::RoomCategoryResponse, RoomError> {
        let user_id = ctx.user_id;
        let model = room_category::Entity::find_by_id(category_id)
            .one(&self.db)
            .await?
            .ok_or_else(|| RoomError::NotFound("Room category not found".to_string()))?;
        self.require_project_admin(model.project, user_id).await?;

        let mut active: room_category::ActiveModel = model.into();
        if let Some(name) = request.name {
            active.name = Set(name);
        }
        if let Some(position) = request.position {
            active.position = Set(position);
        }
        let updated = active.update(&self.db).await?;

        let event = ProjectRoomEvent {
            event_type: super::RoomEventType::CategoryUpdated.as_str().into(),
            project_id: updated.project,
            room_id: None,
            category_id: Some(updated.id),
            message_id: None,
            seq: None,
            timestamp: Utc::now(),
        };
        let _ = self
            .queue
            .publish_project_room_event(updated.project, event)
            .await;

        Ok(super::RoomCategoryResponse::from(updated))
    }

    pub async fn room_category_delete(
        &self,
        category_id: Uuid,
        ctx: &WsUserContext,
    ) -> Result<(), RoomError> {
        let user_id = ctx.user_id;
        let model = room_category::Entity::find_by_id(category_id)
            .one(&self.db)
            .await?
            .ok_or_else(|| RoomError::NotFound("Room category not found".to_string()))?;
        self.require_project_admin(model.project, user_id).await?;
        let project_id = model.project;

        let txn = self.db.begin().await?;

        room::Entity::update_many()
            .col_expr(room::Column::Category, Expr::value(None::<Uuid>))
            .filter(room::Column::Category.eq(category_id))
            .exec(&txn)
            .await?;

        room_category::Entity::delete_by_id(category_id)
            .exec(&txn)
            .await?;

        txn.commit().await?;

        let event = ProjectRoomEvent {
            event_type: super::RoomEventType::CategoryDeleted.as_str().into(),
            project_id,
            room_id: None,
            category_id: Some(category_id),
            message_id: None,
            seq: None,
            timestamp: Utc::now(),
        };
        let _ = self
            .queue
            .publish_project_room_event(project_id, event)
            .await;

        Ok(())
    }
}
