use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::error::ApiError;
use room::{
    RoomCategoryResponse, RoomMemberResponse, RoomMessageListResponse, RoomMessageResponse,
    RoomPinResponse, RoomResponse, RoomThreadResponse, UserInfo,
};

#[derive(Debug, Clone, Deserialize)]
pub struct WsRequest {
    pub request_id: Uuid,
    pub action: WsAction,
    #[serde(default)]
    pub params: WsRequestParams,
}

impl WsRequest {
    pub fn params(&self) -> &WsRequestParams {
        &self.params
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
pub enum WsAction {
    #[serde(rename = "room.list")]
    RoomList,
    #[serde(rename = "room.get")]
    RoomGet,
    #[serde(rename = "room.create")]
    RoomCreate,
    #[serde(rename = "room.update")]
    RoomUpdate,
    #[serde(rename = "room.delete")]
    RoomDelete,
    #[serde(rename = "category.list")]
    CategoryList,
    #[serde(rename = "category.create")]
    CategoryCreate,
    #[serde(rename = "category.update")]
    CategoryUpdate,
    #[serde(rename = "category.delete")]
    CategoryDelete,
    #[serde(rename = "message.list")]
    MessageList,
    #[serde(rename = "message.create")]
    MessageCreate,
    #[serde(rename = "message.update")]
    MessageUpdate,
    #[serde(rename = "message.revoke")]
    MessageRevoke,
    #[serde(rename = "message.get")]
    MessageGet,
    #[serde(rename = "member.list")]
    MemberList,
    #[serde(rename = "member.add")]
    MemberAdd,
    #[serde(rename = "member.remove")]
    MemberRemove,
    #[serde(rename = "member.leave")]
    MemberLeave,
    #[serde(rename = "member.set_read_seq")]
    MemberSetReadSeq,
    #[serde(rename = "member.update_role")]
    MemberUpdateRole,
    #[serde(rename = "pin.list")]
    PinList,
    #[serde(rename = "pin.add")]
    PinAdd,
    #[serde(rename = "pin.remove")]
    PinRemove,
    #[serde(rename = "thread.list")]
    ThreadList,
    #[serde(rename = "thread.create")]
    ThreadCreate,
    #[serde(rename = "thread.messages")]
    ThreadMessages,
    #[serde(rename = "reaction.add")]
    ReactionAdd,
    #[serde(rename = "reaction.remove")]
    ReactionRemove,
    #[serde(rename = "reaction.get")]
    ReactionGet,
    #[serde(rename = "reaction.list_batch")]
    ReactionListBatch,
    #[serde(rename = "message.search")]
    MessageSearch,
    #[serde(rename = "message.edit_history")]
    MessageEditHistory,
    #[serde(rename = "ai.list")]
    AiList,
    #[serde(rename = "ai.upsert")]
    AiUpsert,
    #[serde(rename = "ai.delete")]
    AiDelete,
    #[serde(rename = "notification.list")]
    NotificationList,
    #[serde(rename = "notification.mark_read")]
    NotificationMarkRead,
    #[serde(rename = "notification.mark_all_read")]
    NotificationMarkAllRead,
    #[serde(rename = "notification.archive")]
    NotificationArchive,
    #[serde(rename = "mention.list")]
    MentionList,
    #[serde(rename = "mention.read_all")]
    MentionReadAll,
    #[serde(rename = "room.subscribe")]
    SubscribeRoom,
    #[serde(rename = "room.unsubscribe")]
    UnsubscribeRoom,
    #[serde(rename = "project.subscribe")]
    SubscribeProject,
    #[serde(rename = "project.unsubscribe")]
    UnsubscribeProject,
}

impl std::fmt::Display for WsAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            WsAction::RoomList => write!(f, "room.list"),
            WsAction::RoomGet => write!(f, "room.get"),
            WsAction::RoomCreate => write!(f, "room.create"),
            WsAction::RoomUpdate => write!(f, "room.update"),
            WsAction::RoomDelete => write!(f, "room.delete"),
            WsAction::CategoryList => write!(f, "category.list"),
            WsAction::CategoryCreate => write!(f, "category.create"),
            WsAction::CategoryUpdate => write!(f, "category.update"),
            WsAction::CategoryDelete => write!(f, "category.delete"),
            WsAction::MessageList => write!(f, "message.list"),
            WsAction::MessageCreate => write!(f, "message.create"),
            WsAction::MessageUpdate => write!(f, "message.update"),
            WsAction::MessageRevoke => write!(f, "message.revoke"),
            WsAction::MessageGet => write!(f, "message.get"),
            WsAction::MemberList => write!(f, "member.list"),
            WsAction::MemberAdd => write!(f, "member.add"),
            WsAction::MemberRemove => write!(f, "member.remove"),
            WsAction::MemberLeave => write!(f, "member.leave"),
            WsAction::MemberSetReadSeq => write!(f, "member.set_read_seq"),
            WsAction::MemberUpdateRole => write!(f, "member.update_role"),
            WsAction::PinList => write!(f, "pin.list"),
            WsAction::PinAdd => write!(f, "pin.add"),
            WsAction::PinRemove => write!(f, "pin.remove"),
            WsAction::ThreadList => write!(f, "thread.list"),
            WsAction::ThreadCreate => write!(f, "thread.create"),
            WsAction::ThreadMessages => write!(f, "thread.messages"),
            WsAction::ReactionAdd => write!(f, "reaction.add"),
            WsAction::ReactionRemove => write!(f, "reaction.remove"),
            WsAction::ReactionGet => write!(f, "reaction.get"),
            WsAction::ReactionListBatch => write!(f, "reaction.list_batch"),
            WsAction::MessageSearch => write!(f, "message.search"),
            WsAction::MessageEditHistory => write!(f, "message.edit_history"),
            WsAction::AiList => write!(f, "ai.list"),
            WsAction::AiUpsert => write!(f, "ai.upsert"),
            WsAction::AiDelete => write!(f, "ai.delete"),
            WsAction::NotificationList => write!(f, "notification.list"),
            WsAction::NotificationMarkRead => write!(f, "notification.mark_read"),
            WsAction::NotificationMarkAllRead => write!(f, "notification.mark_all_read"),
            WsAction::NotificationArchive => write!(f, "notification.archive"),
            WsAction::MentionList => write!(f, "mention.list"),
            WsAction::MentionReadAll => write!(f, "mention.read_all"),
            WsAction::SubscribeRoom => write!(f, "room.subscribe"),
            WsAction::UnsubscribeRoom => write!(f, "room.unsubscribe"),
            WsAction::SubscribeProject => write!(f, "project.subscribe"),
            WsAction::UnsubscribeProject => write!(f, "project.unsubscribe"),
        }
    }
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct WsRequestParams {
    pub project_name: Option<String>,
    pub room_id: Option<Uuid>,
    pub message_id: Option<Uuid>,
    pub message_ids: Option<Vec<Uuid>>,
    pub user_id: Option<Uuid>,
    pub category_id: Option<Uuid>,
    pub thread_id: Option<Uuid>,
    pub model_id: Option<Uuid>,
    pub notification_id: Option<Uuid>,
    pub emoji: Option<String>,
    pub only_public: Option<bool>,
    pub only_unread: Option<bool>,
    pub archived: Option<bool>,
    pub limit: Option<u64>,
    pub offset: Option<u64>,
    pub before_seq: Option<i64>,
    pub after_seq: Option<i64>,
    pub room_name: Option<String>,
    pub room_public: Option<bool>,
    pub room_category: Option<Uuid>,
    pub content: Option<String>,
    pub content_type: Option<String>,
    pub in_reply_to: Option<Uuid>,
    pub parent_seq: Option<i64>,
    pub role: Option<String>,
    pub last_read_seq: Option<i64>,
    pub name: Option<String>,
    pub position: Option<i32>,
    pub model: Option<Uuid>,
    pub model_version: Option<Uuid>,
    pub history_limit: Option<i64>,
    pub system_prompt: Option<String>,
    pub temperature: Option<f64>,
    pub max_tokens: Option<i64>,
    pub use_exact: Option<bool>,
    pub think: Option<bool>,
    pub stream: Option<bool>,
    pub min_score: Option<f32>,
    pub query: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct WsResponse {
    pub request_id: Uuid,
    pub action: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<WsResponseData>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<WsErrorInfo>,
}

impl WsResponse {
    pub fn success(request_id: Uuid, action: &str, data: WsResponseData) -> Self {
        Self {
            request_id,
            action: action.to_string(),
            data: Some(data),
            error: None,
        }
    }

    pub fn error_response(
        request_id: Uuid,
        action: &str,
        code: i32,
        error: &str,
        message: &str,
    ) -> Self {
        Self {
            request_id,
            action: action.to_string(),
            data: None,
            error: Some(WsErrorInfo {
                code,
                error: error.to_string(),
                message: message.to_string(),
            }),
        }
    }

    pub fn from_api_error(request_id: Uuid, action: &str, err: ApiError) -> Self {
        let err_code = err.0.code();
        let slug = err.0.slug();
        let msg = err.0.user_message();
        Self::error_response(request_id, action, err_code, slug, &msg)
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct WsErrorInfo {
    pub code: i32,
    pub error: String,
    pub message: String,
}

#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum WsResponseData {
    Bool(bool),
    U64(u64),
    Room(Box<RoomResponse>),
    RoomList(Vec<RoomResponse>),
    Category(Box<RoomCategoryResponse>),
    CategoryList(Vec<RoomCategoryResponse>),
    Message(Box<RoomMessageResponse>),
    MessageList(Box<RoomMessageListResponse>),
    Member(Box<RoomMemberResponse>),
    MemberList(Vec<RoomMemberResponse>),
    Pin(Box<PinResponseData>),
    PinList(Vec<PinResponseData>),
    Thread(Box<RoomThreadResponse>),
    ThreadList(Vec<RoomThreadResponse>),
    ReactionList(ReactionListData),
    ReactionListBatch(Vec<ReactionListData>),
    SearchResult(SearchResultData),
    EditHistory(MessageEditHistoryResponse),
    AiList(Vec<AiConfigData>),
    AiConfig(Box<AiConfigData>),
    NotificationList(NotificationListData),
    MentionList(MentionListData),
    Subscribed(SubscribeData),
    UserInfo(Vec<UserInfo>),
}

impl WsResponseData {
    pub fn room(room: RoomResponse) -> Self {
        WsResponseData::Room(Box::new(room))
    }

    pub fn room_list(rooms: Vec<RoomResponse>) -> Self {
        WsResponseData::RoomList(rooms)
    }

    pub fn category(category: RoomCategoryResponse) -> Self {
        WsResponseData::Category(Box::new(category))
    }

    pub fn category_list(categories: Vec<RoomCategoryResponse>) -> Self {
        WsResponseData::CategoryList(categories)
    }

    pub fn message(message: RoomMessageResponse) -> Self {
        WsResponseData::Message(Box::new(message))
    }

    pub fn message_list(messages: RoomMessageListResponse) -> Self {
        WsResponseData::MessageList(Box::new(messages))
    }

    pub fn member(member: RoomMemberResponse) -> Self {
        WsResponseData::Member(Box::new(member))
    }

    pub fn member_list(members: Vec<RoomMemberResponse>) -> Self {
        WsResponseData::MemberList(members)
    }

    pub fn pin(pin: RoomPinResponse) -> Self {
        WsResponseData::Pin(Box::new(PinResponseData {
            room: pin.room,
            message: pin.message,
            pinned_by: pin.pinned_by,
            pinned_at: pin.pinned_at,
            message_data: None,
        }))
    }

    pub fn pin_list(pins: Vec<RoomPinResponse>) -> Self {
        WsResponseData::PinList(
            pins.into_iter()
                .map(|p| PinResponseData {
                    room: p.room,
                    message: p.message,
                    pinned_by: p.pinned_by,
                    pinned_at: p.pinned_at,
                    message_data: None,
                })
                .collect(),
        )
    }

    pub fn thread(thread: RoomThreadResponse) -> Self {
        WsResponseData::Thread(Box::new(thread))
    }

    pub fn thread_list(threads: Vec<RoomThreadResponse>) -> Self {
        WsResponseData::ThreadList(threads)
    }

    pub fn bool(b: bool) -> Self {
        WsResponseData::Bool(b)
    }

    pub fn u64(n: u64) -> Self {
        WsResponseData::U64(n)
    }

    pub fn subscribed(room_id: Option<Uuid>, project_id: Option<Uuid>) -> Self {
        WsResponseData::Subscribed(SubscribeData {
            room_id,
            project_id,
        })
    }

    pub fn user_info(users: Vec<UserInfo>) -> Self {
        WsResponseData::UserInfo(users)
    }

    pub fn reaction_list(data: room::MessageReactionsResponse) -> Self {
        WsResponseData::ReactionList(ReactionListData::from(data))
    }

    pub fn reaction_list_batch(data: Vec<room::MessageReactionsResponse>) -> Self {
        WsResponseData::ReactionListBatch(data.into_iter().map(ReactionListData::from).collect())
    }

    pub fn search_result(data: room::MessageSearchResponse) -> Self {
        WsResponseData::SearchResult(SearchResultData::from(data))
    }

    pub fn edit_history(data: room::MessageEditHistoryResponse) -> Self {
        WsResponseData::EditHistory(MessageEditHistoryResponse::from(data))
    }

    pub fn ai_list(configs: Vec<room::RoomAiResponse>) -> Self {
        WsResponseData::AiList(configs.into_iter().map(AiConfigData::from).collect())
    }

    pub fn ai_config(config: room::RoomAiResponse) -> Self {
        WsResponseData::AiConfig(Box::new(AiConfigData::from(config)))
    }

    pub fn notification_list(data: room::NotificationListResponse) -> Self {
        WsResponseData::NotificationList(NotificationListData::from(data))
    }

    pub fn mention_list(mentions: Vec<room::MentionNotificationResponse>) -> Self {
        WsResponseData::MentionList(MentionListData::from(mentions))
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct PinResponseData {
    pub room: Uuid,
    pub message: Uuid,
    pub pinned_by: Uuid,
    pub pinned_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_data: Option<RoomMessageResponse>,
}

#[derive(Debug, Clone, Serialize)]
pub struct ReactionListData {
    pub message_id: Uuid,
    pub reactions: Vec<ReactionItem>,
}

impl From<room::MessageReactionsResponse> for ReactionListData {
    fn from(r: room::MessageReactionsResponse) -> Self {
        Self {
            message_id: r.message_id,
            reactions: r
                .reactions
                .into_iter()
                .map(|g| ReactionItem {
                    emoji: g.emoji,
                    count: g.count,
                    reacted_by_me: g.reacted_by_me,
                    users: g.users,
                })
                .collect(),
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct ReactionItem {
    pub emoji: String,
    pub count: i64,
    pub reacted_by_me: bool,
    pub users: Vec<Uuid>,
}

#[derive(Debug, Clone, Serialize)]
pub struct SearchResultData {
    pub messages: Vec<RoomMessageResponse>,
    pub total: i64,
}

impl From<room::MessageSearchResponse> for SearchResultData {
    fn from(r: room::MessageSearchResponse) -> Self {
        Self {
            messages: r.messages,
            total: r.total,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct MessageEditHistoryResponse {
    pub message_id: Uuid,
    pub history: Vec<EditHistoryEntry>,
    pub total_edits: i64,
}

impl From<room::MessageEditHistoryResponse> for MessageEditHistoryResponse {
    fn from(r: room::MessageEditHistoryResponse) -> Self {
        Self {
            message_id: r.message_id,
            history: r
                .history
                .into_iter()
                .map(|h| EditHistoryEntry {
                    old_content: h.old_content,
                    new_content: h.new_content,
                    edited_at: h.edited_at,
                })
                .collect(),
            total_edits: r.total_edits,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct EditHistoryEntry {
    pub old_content: String,
    pub new_content: String,
    pub edited_at: chrono::DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize)]
pub struct AiConfigData {
    pub room: Uuid,
    pub model: Uuid,
    pub version: Option<Uuid>,
    pub call_count: i64,
    pub last_call_at: Option<DateTime<Utc>>,
    pub history_limit: Option<i64>,
    pub system_prompt: Option<String>,
    pub temperature: Option<f64>,
    pub max_tokens: Option<i64>,
    pub use_exact: bool,
    pub think: bool,
    pub stream: bool,
    pub min_score: Option<f32>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

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

#[derive(Debug, Clone, Serialize)]
pub struct NotificationListData {
    pub notifications: Vec<NotificationData>,
    pub total: i64,
    pub unread_count: i64,
}

#[derive(Debug, Clone, Serialize)]
pub struct NotificationData {
    pub id: Uuid,
    pub room: Option<Uuid>,
    pub project: Option<Uuid>,
    pub user_id: Option<Uuid>,
    pub user_info: Option<UserInfo>,
    pub notification_type: String,
    pub title: String,
    pub content: Option<String>,
    pub related_message_id: Option<Uuid>,
    pub related_user_id: Option<Uuid>,
    pub related_room_id: Option<Uuid>,
    pub is_read: bool,
    pub is_archived: bool,
    pub created_at: DateTime<Utc>,
    pub read_at: Option<DateTime<Utc>>,
    pub expires_at: Option<DateTime<Utc>>,
}

impl From<room::NotificationListResponse> for NotificationListData {
    fn from(r: room::NotificationListResponse) -> Self {
        Self {
            notifications: r
                .notifications
                .into_iter()
                .map(|n| NotificationData {
                    id: n.id,
                    room: n.room,
                    project: n.project,
                    user_id: n.user_id,
                    user_info: n.user_info,
                    notification_type: n.notification_type,
                    title: n.title,
                    content: n.content,
                    related_message_id: n.related_message_id,
                    related_user_id: n.related_user_id,
                    related_room_id: n.related_room_id,
                    is_read: n.is_read,
                    is_archived: n.is_archived,
                    created_at: n.created_at,
                    read_at: n.read_at,
                    expires_at: n.expires_at,
                })
                .collect(),
            total: r.total,
            unread_count: r.unread_count,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct MentionListData {
    pub mentions: Vec<MentionData>,
}

#[derive(Debug, Clone, Serialize)]
pub struct MentionData {
    pub message_id: Uuid,
    pub mentioned_by: Uuid,
    pub mentioned_by_name: String,
    pub content_preview: String,
    pub room_id: Uuid,
    pub room_name: String,
    pub created_at: DateTime<Utc>,
}

impl From<Vec<room::MentionNotificationResponse>> for MentionListData {
    fn from(mentions: Vec<room::MentionNotificationResponse>) -> Self {
        Self {
            mentions: mentions
                .into_iter()
                .map(|m| MentionData {
                    message_id: m.message_id,
                    mentioned_by: m.mentioned_by,
                    mentioned_by_name: m.mentioned_by_name,
                    content_preview: m.content_preview,
                    room_id: m.room_id,
                    room_name: m.room_name,
                    created_at: m.created_at,
                })
                .collect(),
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct SubscribeData {
    pub room_id: Option<Uuid>,
    pub project_id: Option<Uuid>,
}
