use crate::error::RoomError;
use crate::service::RoomService;
use models::agents::model as ai_model;
use models::projects::{MemberRole, project, project_history_name, project_members};
use models::rooms::{
    MessageContentType, RoomMemberRole, room, room_ai, room_category, room_member, room_message,
    room_notifications, room_pin, room_thread,
};
use models::users::user as user_model;
use sea_orm::*;
use uuid::Uuid;

impl From<room_category::Model> for super::RoomCategoryResponse {
    fn from(value: room_category::Model) -> Self {
        Self {
            id: value.id,
            project: value.project,
            name: value.name,
            position: value.position,
            created_by: value.created_by,
            created_at: value.created_at,
        }
    }
}

impl From<room::Model> for super::RoomResponse {
    fn from(value: room::Model) -> Self {
        Self {
            id: value.id,
            project: value.project,
            room_name: value.room_name,
            public: value.public,
            category: value.category,
            created_by: value.created_by,
            created_at: value.created_at,
            last_msg_at: value.last_msg_at,
            unread_count: 0,
        }
    }
}

impl From<room_member::Model> for super::RoomMemberResponse {
    fn from(value: room_member::Model) -> Self {
        Self {
            room: value.room,
            user: value.user,
            user_info: None,
            role: value.role.to_string(),
            first_msg_in: value.first_msg_in,
            joined_at: value.joined_at,
            last_read_seq: value.last_read_seq,
            do_not_disturb: value.do_not_disturb,
            dnd_start_hour: value.dnd_start_hour,
            dnd_end_hour: value.dnd_end_hour,
        }
    }
}

impl From<room_message::Model> for super::RoomMessageResponse {
    fn from(value: room_message::Model) -> Self {
        Self {
            id: value.id,
            seq: value.seq,
            room: value.room,
            sender_type: value.sender_type.to_string(),
            sender_id: value.sender_id,
            display_name: None,
            thread: value.thread,
            content: value.content,
            content_type: value.content_type.to_string(),
            edited_at: value.edited_at,
            send_at: value.send_at,
            revoked: value.revoked,
            revoked_by: value.revoked_by,
            in_reply_to: value.in_reply_to,
        }
    }
}

impl From<room_thread::Model> for super::RoomThreadResponse {
    fn from(value: room_thread::Model) -> Self {
        Self {
            id: value.id,
            room: value.room,
            parent: value.parent,
            created_by: value.created_by,
            participants: value.participants,
            last_message_at: value.last_message_at,
            last_message_preview: value.last_message_preview,
            created_at: value.created_at,
            updated_at: value.updated_at,
        }
    }
}

impl From<room_pin::Model> for super::RoomPinResponse {
    fn from(value: room_pin::Model) -> Self {
        Self {
            room: value.room,
            message: value.message,
            pinned_by: value.pinned_by,
            pinned_at: value.pinned_at,
        }
    }
}

impl From<room_ai::Model> for super::RoomAiResponse {
    fn from(value: room_ai::Model) -> Self {
        Self {
            room: value.room,
            model: value.model,
            version: value.version,
            call_count: value.call_count,
            last_call_at: value.last_call_at,
            history_limit: value.history_limit,
            system_prompt: value.system_prompt,
            temperature: value.temperature,
            max_tokens: value.max_tokens,
            use_exact: value.use_exact,
            think: value.think,
            stream: value.stream,
            min_score: value.min_score,
            created_at: value.created_at,
            updated_at: value.updated_at,
        }
    }
}

impl From<room_notifications::Model> for super::NotificationResponse {
    fn from(value: room_notifications::Model) -> Self {
        Self {
            id: value.id,
            room: value.room,
            project: value.project,
            user_id: value.user_id,
            user_info: None,
            notification_type: value.notification_type.to_string(),
            title: value.title,
            content: value.content,
            related_message_id: value.related_message_id,
            related_user_id: value.related_user_id,
            related_room_id: value.related_room_id,
            metadata: value.metadata.unwrap_or(serde_json::json!({})),
            is_read: value.is_read,
            is_archived: value.is_archived,
            created_at: value.created_at,
            read_at: value.read_at,
            expires_at: value.expires_at,
        }
    }
}

impl RoomService {
    pub(crate) fn parse_room_member_role(role: &str) -> Result<RoomMemberRole, RoomError> {
        match role {
            "owner" => Ok(RoomMemberRole::Owner),
            "admin" => Ok(RoomMemberRole::Admin),
            "member" => Ok(RoomMemberRole::Member),
            "guest" => Ok(RoomMemberRole::Guest),
            _ => Err(RoomError::BadRequest("invalid room role".to_string())),
        }
    }

    pub(crate) fn parse_message_content_type(
        content_type: Option<String>,
    ) -> Result<MessageContentType, RoomError> {
        match content_type
            .unwrap_or_else(|| "text".to_string())
            .to_lowercase()
            .as_str()
        {
            "text" => Ok(MessageContentType::Text),
            "image" => Ok(MessageContentType::Image),
            "audio" => Ok(MessageContentType::Audio),
            "video" => Ok(MessageContentType::Video),
            "file" => Ok(MessageContentType::File),
            _ => Err(RoomError::BadRequest(
                "invalid message content_type".to_string(),
            )),
        }
    }

    pub(crate) async fn find_room_member(
        &self,
        room_id: Uuid,
        user_id: Uuid,
    ) -> Result<Option<room_member::Model>, RoomError> {
        room_member::Entity::find_by_id((room_id, user_id))
            .one(&self.db)
            .await
            .map_err(RoomError::from)
    }

    pub(crate) async fn require_room_member_model(
        &self,
        room_id: Uuid,
        user_id: Uuid,
    ) -> Result<room_member::Model, RoomError> {
        self.find_room_member(room_id, user_id)
            .await?
            .ok_or(RoomError::NoPower)
    }

    pub(crate) fn is_room_admin(role: &RoomMemberRole) -> bool {
        matches!(role, RoomMemberRole::Owner | RoomMemberRole::Admin)
    }

    pub(crate) async fn require_room_admin(
        &self,
        room_id: Uuid,
        user_id: Uuid,
    ) -> Result<room_member::Model, RoomError> {
        let member = self.require_room_member_model(room_id, user_id).await?;
        if Self::is_room_admin(&member.role) {
            Ok(member)
        } else {
            Err(RoomError::NoPower)
        }
    }

    pub(crate) async fn require_project_admin(
        &self,
        project_id: Uuid,
        user_id: Uuid,
    ) -> Result<project_members::Model, RoomError> {
        let member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project_id))
            .filter(project_members::Column::User.eq(user_id))
            .one(&self.db)
            .await?
            .ok_or(RoomError::NoPower)?;
        let role = member.scope_role().map_err(|_| RoomError::RoleParseError)?;
        if matches!(role, MemberRole::Owner | MemberRole::Admin) {
            Ok(member)
        } else {
            Err(RoomError::NoPower)
        }
    }

    pub(crate) async fn ensure_room_visible_for_user(
        &self,
        room: &room::Model,
        user_id: Uuid,
    ) -> Result<(), RoomError> {
        if self.find_room_member(room.id, user_id).await?.is_some() {
            return Ok(());
        }
        let project_member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(room.project))
            .filter(project_members::Column::User.eq(user_id))
            .one(&self.db)
            .await?;
        if room.public && project_member.is_some() {
            Ok(())
        } else {
            Err(RoomError::NoPower)
        }
    }

    pub(crate) async fn next_room_message_seq<C>(
        &self,
        room_id: Uuid,
        db: &C,
    ) -> Result<i64, RoomError>
    where
        C: ConnectionTrait,
    {
        let max_seq: Option<Option<i64>> = room_message::Entity::find()
            .filter(room_message::Column::Room.eq(room_id))
            .select_only()
            .column_as(room_message::Column::Seq.max(), "max_seq")
            .into_tuple::<Option<i64>>()
            .one(db)
            .await?;
        Ok(max_seq.flatten().unwrap_or(0) + 1)
    }

    pub async fn utils_find_project_by_name(
        &self,
        name: String,
    ) -> Result<project::Model, RoomError> {
        match project::Entity::find()
            .filter(project::Column::Name.eq(name.clone()))
            .one(&self.db)
            .await
            .ok()
            .flatten()
        {
            Some(project) => Ok(project),
            None => match project_history_name::Entity::find()
                .filter(project_history_name::Column::HistoryName.eq(name))
                .one(&self.db)
                .await
                .ok()
                .flatten()
            {
                Some(project) => self.utils_find_project_by_uid(project.project_uid).await,
                None => Err(RoomError::NotFound("Project not found".to_string())),
            },
        }
    }

    pub async fn utils_find_project_by_uid(&self, uid: Uuid) -> Result<project::Model, RoomError> {
        project::Entity::find_by_id(uid)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .ok_or_else(|| RoomError::NotFound("Project not found".to_string()))
    }

    pub async fn check_project_access(
        &self,
        project_uid: Uuid,
        user_uid: Uuid,
    ) -> Result<(), RoomError> {
        let project = project::Entity::find_by_id(project_uid)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .ok_or_else(|| RoomError::NotFound("Project not found".to_string()))?;

        if project.is_public {
            return Ok(());
        }

        let member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project_uid))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?;

        if member.is_some() {
            Ok(())
        } else {
            Err(RoomError::NoPower)
        }
    }

    pub(crate) fn validate_name(name: &str, max_len: usize) -> Result<(), RoomError> {
        if name.trim().is_empty() {
            return Err(RoomError::BadRequest("name cannot be empty".to_string()));
        }
        if name.len() > max_len {
            return Err(RoomError::BadRequest(format!(
                "name exceeds maximum length of {} characters",
                max_len
            )));
        }
        Ok(())
    }

    pub(crate) fn validate_content(content: &str, max_len: usize) -> Result<(), RoomError> {
        if content.trim().is_empty() {
            return Err(RoomError::BadRequest("content cannot be empty".to_string()));
        }
        if content.len() > max_len {
            return Err(RoomError::BadRequest(format!(
                "content exceeds maximum length of {} characters",
                max_len
            )));
        }
        Ok(())
    }

    pub(crate) fn sanitize_content(content: &str) -> String {
        use std::sync::LazyLock;

        static SCRIPT_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)<script[^>]*>.*?</script>").unwrap());
        static STYLE_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)<style[^>]*>.*?</style>").unwrap());
        static ONERROR_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)\bonerror\s*=").unwrap());
        static ONLOAD_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)\bonload\s*=").unwrap());
        static ONCLICK_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)\bonclick\s*=").unwrap());
        static ONMOUSEOVER_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)\bonmouseover\s*=").unwrap());
        static JAVASCRIPT_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)javascript:").unwrap());
        static DATA_RE: LazyLock<regex_lite::Regex, fn() -> regex_lite::Regex> =
            LazyLock::new(|| regex_lite::Regex::new(r"(?i)data:").unwrap());

        let mut result = content.to_string();
        result = SCRIPT_RE.replace_all(&result, "").to_string();
        result = STYLE_RE.replace_all(&result, "").to_string();
        result = ONERROR_RE.replace_all(&result, "blocked=").to_string();
        result = ONLOAD_RE.replace_all(&result, "blocked=").to_string();
        result = ONCLICK_RE.replace_all(&result, "blocked=").to_string();
        result = ONMOUSEOVER_RE.replace_all(&result, "blocked=").to_string();
        result = JAVASCRIPT_RE.replace_all(&result, "blocked:").to_string();
        result = DATA_RE.replace_all(&result, "blocked:").to_string();

        result
    }

    pub async fn resolve_display_name(
        &self,
        msg: room_message::Model,
        _room_id: Uuid,
    ) -> super::RoomMessageResponse {
        let sender_type = msg.sender_type.to_string();
        let display_name = match sender_type.as_str() {
            "ai" => {
                if let Some(sender_id) = msg.sender_id {
                    ai_model::Entity::find_by_id(sender_id)
                        .one(&self.db)
                        .await
                        .ok()
                        .flatten()
                        .map(|m| m.name)
                } else {
                    None
                }
            }
            _ => {
                if let Some(sender_id) = msg.sender_id {
                    let user = user_model::Entity::find()
                        .filter(user_model::Column::Uid.eq(sender_id))
                        .one(&self.db)
                        .await
                        .ok()
                        .flatten();
                    user.map(|u| u.display_name.unwrap_or_else(|| u.username))
                } else {
                    None
                }
            }
        };

        super::RoomMessageResponse {
            id: msg.id,
            seq: msg.seq,
            room: msg.room,
            sender_type,
            sender_id: msg.sender_id,
            display_name,
            thread: msg.thread,
            content: msg.content,
            content_type: msg.content_type.to_string(),
            edited_at: msg.edited_at,
            send_at: msg.send_at,
            revoked: msg.revoked,
            revoked_by: msg.revoked_by,
            in_reply_to: msg.in_reply_to,
        }
    }
}
