use crate::error::RoomError;
use crate::service::RoomService;
use crate::ws_context::WsUserContext;
use chrono::Utc;
use models::rooms::{
    RoomMemberRole, room, room_ai, room_category, room_member, room_message, room_pin, room_thread,
};
use models::projects::{project_members, MemberRole as Role};
use queue::ProjectRoomEvent;
use sea_orm::*;
use uuid::Uuid;

impl RoomService {
    /// Cache TTL for room list (in seconds).
    const ROOM_LIST_CACHE_TTL: u64 = 60;

    pub async fn room_list(
        &self,
        project_name: String,
        only_public: Option<bool>,
        ctx: &WsUserContext,
    ) -> Result<Vec<super::RoomResponse>, 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?;

        // Try cache first
        let cache_key = format!(
            "room:list:{}:{}:public={}",
            project.id,
            user_id,
            only_public.unwrap_or(false)
        );

        if let Ok(mut conn) = self.cache.conn().await {
            if let Ok(Some(cached)) = redis::cmd("GET")
                .arg(&cache_key)
                .query_async::<Option<String>>(&mut conn)
                .await
            {
                if let Ok(responses) = serde_json::from_str::<Vec<super::RoomResponse>>(&cached) {
                    tracing::debug!(cache_key = %cache_key, "room_list: cache hit");
                    return Ok(responses);
                }
            }
        }

        tracing::debug!(cache_key = %cache_key, "room_list: cache miss");

        let mut query = room::Entity::find().filter(room::Column::Project.eq(project.id));
        if only_public.unwrap_or(false) {
            query = query.filter(room::Column::Public.eq(true));
        }

        let models = query
            .order_by_desc(room::Column::LastMsgAt)
            .all(&self.db)
            .await?;

        let room_ids: Vec<Uuid> = models.iter().map(|r| r.id).collect();

        let latest_seqs: std::collections::HashMap<Uuid, i64> = room_message::Entity::find()
            .select_only()
            .column(room_message::Column::Room)
            .column_as(room_message::Column::Seq.max(), "max_seq")
            .filter(room_message::Column::Room.is_in(room_ids.clone()))
            .group_by(room_message::Column::Room)
            .into_tuple::<(Uuid, Option<i64>)>()
            .all(&self.db)
            .await?
            .into_iter()
            .map(|(room, seq)| (room, seq.unwrap_or(0)))
            .collect();

        let member_read_seqs: std::collections::HashMap<Uuid, i64> = room_member::Entity::find()
            .filter(room_member::Column::User.eq(user_id))
            .filter(room_member::Column::Room.is_in(room_ids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|m| (m.room, m.last_read_seq.unwrap_or(0)))
            .collect();

        let mut responses = Vec::new();
        for model in models {
            let last_read_seq = member_read_seqs.get(&model.id).copied().unwrap_or(0);
            let latest_seq = latest_seqs.get(&model.id).copied().unwrap_or(0);
            let unread_count = std::cmp::max(latest_seq - last_read_seq, 0);

            let mut response = super::RoomResponse::from(model);
            response.unread_count = unread_count;
            responses.push(response);
        }

        // Cache the result
        if let Ok(mut conn) = self.cache.conn().await {
            if let Ok(json) = serde_json::to_string(&responses) {
                let _: Option<String> = redis::cmd("SETEX")
                    .arg(&cache_key)
                    .arg(Self::ROOM_LIST_CACHE_TTL)
                    .arg(&json)
                    .query_async(&mut conn)
                    .await
                    .inspect_err(|e| {
                        tracing::warn!(cache_key = %cache_key, error = %e, "room_list: failed to cache");
                    })
                    .ok();
            }
        }

        Ok(responses)
    }

    pub async fn room_create(
        &self,
        project_name: String,
        request: super::RoomCreateRequest,
        ctx: &WsUserContext,
    ) -> Result<super::RoomResponse, 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.room_name, super::MAX_ROOM_NAME_LEN)?;

        if let Some(category_id) = request.category {
            let category = room_category::Entity::find_by_id(category_id)
                .one(&self.db)
                .await?
                .ok_or_else(|| RoomError::NotFound("Room category not found".to_string()))?;
            if category.project != project.id {
                return Err(RoomError::BadRequest(
                    "category does not belong to this project".to_string(),
                ));
            }
        }

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

        let room_name = request.room_name.clone();
        let room_model = room::ActiveModel {
            id: Set(Uuid::now_v7()),
            project: Set(project.id),
            room_name: Set(request.room_name),
            public: Set(request.public),
            category: Set(request.category),
            created_by: Set(user_id),
            created_at: Set(Utc::now()),
            last_msg_at: Set(Utc::now()),
        }
        .insert(&txn)
        .await?;

        room_member::ActiveModel {
            room: Set(room_model.id),
            user: Set(user_id),
            role: Set(RoomMemberRole::Owner),
            first_msg_in: Set(None),
            joined_at: Set(Some(Utc::now())),
            last_read_seq: Set(None),
            do_not_disturb: Set(false),
            dnd_start_hour: Set(None),
            dnd_end_hour: Set(None),
        }
        .insert(&txn)
        .await?;

        // Inherit project members into room members
        let project_members_list = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .all(&txn)
            .await?;

        for pm in project_members_list {
            if pm.user != user_id {
                let role = match pm.scope_role() {
                    Ok(Role::Owner) => RoomMemberRole::Owner,
                    Ok(Role::Admin) => RoomMemberRole::Admin,
                    Ok(_) | Err(_) => RoomMemberRole::Member,
                };

                room_member::ActiveModel {
                    room: Set(room_model.id),
                    user: Set(pm.user),
                    role: Set(role),
                    first_msg_in: Set(None),
                    joined_at: Set(Some(Utc::now())),
                    last_read_seq: Set(None),
                    do_not_disturb: Set(false),
                    dnd_start_hour: Set(None),
                    dnd_end_hour: Set(None),
                }
                .insert(&txn)
                .await
                .ok();
            }
        }

        txn.commit().await?;

        // Invalidate room list cache for this project
        self.invalidate_room_list_cache(project.id).await;

        self.spawn_room_workers(room_model.id);

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

        self.notify_project_members(
            project.id,
            super::NotificationType::RoomCreated,
            format!("新房间已创建: {}", room_name),
            None,
            Some(room_model.id),
        );

        Ok(super::RoomResponse::from(room_model))
    }

    pub async fn room_get(
        &self,
        room_id: Uuid,
        ctx: &WsUserContext,
    ) -> Result<super::RoomResponse, RoomError> {
        let user_id = ctx.user_id;
        let model = self.find_room_or_404(room_id).await?;
        self.ensure_room_visible_for_user(&model, user_id).await?;
        Ok(super::RoomResponse::from(model))
    }

    pub async fn room_update(
        &self,
        room_id: Uuid,
        request: super::RoomUpdateRequest,
        ctx: &WsUserContext,
    ) -> Result<super::RoomResponse, RoomError> {
        let user_id = ctx.user_id;
        let room_model = self.find_room_or_404(room_id).await?;
        self.require_room_admin(room_id, user_id).await?;

        if let Some(category_id) = request.category {
            let category = room_category::Entity::find_by_id(category_id)
                .one(&self.db)
                .await?
                .ok_or_else(|| RoomError::NotFound("Room category not found".to_string()))?;
            if category.project != room_model.project {
                return Err(RoomError::BadRequest(
                    "category does not belong to this project".to_string(),
                ));
            }
        }

        let mut active: room::ActiveModel = room_model.into();

        let renamed = request.room_name.is_some();
        let moved = request.category.is_some();

        if let Some(room_name) = request.room_name {
            active.room_name = Set(room_name);
        }
        if let Some(public) = request.public {
            active.public = Set(public);
        }
        if request.category.is_some() {
            active.category = Set(request.category);
        }
        let updated = active.update(&self.db).await?;

        // Invalidate room list cache
        self.invalidate_room_list_cache(updated.project).await;

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

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

    pub async fn room_delete(&self, room_id: Uuid, ctx: &WsUserContext) -> Result<(), RoomError> {
        let user_id = ctx.user_id;
        let room_model = self.find_room_or_404(room_id).await?;
        self.require_room_admin(room_id, user_id).await?;
        let project_id = room_model.project;

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

        room_message::Entity::delete_many()
            .filter(room_message::Column::Room.eq(room_id))
            .exec(&txn)
            .await?;

        room_pin::Entity::delete_many()
            .filter(room_pin::Column::Room.eq(room_id))
            .exec(&txn)
            .await?;

        room_thread::Entity::delete_many()
            .filter(room_thread::Column::Room.eq(room_id))
            .exec(&txn)
            .await?;

        room_member::Entity::delete_many()
            .filter(room_member::Column::Room.eq(room_id))
            .exec(&txn)
            .await?;

        room_ai::Entity::delete_many()
            .filter(room_ai::Column::Room.eq(room_id))
            .exec(&txn)
            .await?;

        room::Entity::delete_by_id(room_id).exec(&txn).await?;

        txn.commit().await?;

        // Invalidate room list cache
        self.invalidate_room_list_cache(project_id).await;

        self.room_manager.shutdown_room(room_id).await;

        // Clean up Redis seq key so re-creating the room starts fresh
        let seq_key = format!("room:seq:{}", room_id);
        if let Ok(mut conn) = self.cache.conn().await {
            let _: Option<String> = redis::cmd("DEL")
                .arg(&seq_key)
                .query_async(&mut conn)
                .await
                .inspect_err(|e| {
                    tracing::warn!(seq_key = %seq_key, error = %e, "room_delete: failed to DEL seq key");
                })
                .ok();
        }

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

        self.notify_project_members(
            project_id,
            super::NotificationType::RoomDeleted,
            format!("房间 {} 已被删除", room_model.room_name),
            None,
            Some(room_id),
        );

        Ok(())
    }

    /// Invalidate all room list cache entries for a project.
    async fn invalidate_room_list_cache(&self, project_id: Uuid) {
        let pattern = format!("room:list:{}:*", project_id);
        if let Ok(mut conn) = self.cache.conn().await {
            // Use SCAN to find matching keys, then DELETE them
            let mut cursor: u64 = 0;
            loop {
                let (new_cursor, keys): (u64, Vec<String>) = match redis::cmd("SCAN")
                    .arg(cursor)
                    .arg("MATCH")
                    .arg(&pattern)
                    .arg("COUNT")
                    .arg(100)
                    .query_async(&mut conn)
                    .await
                {
                    Ok(result) => result,
                    Err(e) => {
                        tracing::warn!(error = %e, "invalidate_room_list_cache: SCAN failed");
                        break;
                    }
                };
                cursor = new_cursor;

                if !keys.is_empty() {
                    // Delete keys in batches
                    let keys_refs: Vec<&str> = keys.iter().map(|s| s.as_str()).collect();
                    if let Err(e) = redis::cmd("DEL")
                        .arg(&keys_refs)
                        .query_async::<i64>(&mut conn)
                        .await
                    {
                        tracing::warn!(error = %e, "invalidate_room_list_cache: DEL failed");
                    } else {
                        tracing::debug!(keys_count = keys.len(), "invalidate_room_list_cache: deleted");
                    }
                }

                if cursor == 0 {
                    break;
                }
            }
        }
    }
}
