use kordophone::model::message::AttachmentMetadata;
use pretty::RcDoc;
use std::collections::HashMap;
use std::fmt::Display;
use time::OffsetDateTime;

#[cfg(target_os = "linux")]
use dbus::arg::{self, RefArg};

pub struct PrintableConversation {
    pub guid: String,
    pub date: OffsetDateTime,
    pub unread_count: i32,
    pub last_message_preview: Option<String>,
    pub participants: Vec<String>,
    pub display_name: Option<String>,
}

impl From<kordophone::model::Conversation> for PrintableConversation {
    fn from(value: kordophone::model::Conversation) -> Self {
        Self {
            guid: value.guid,
            date: value.date,
            unread_count: value.unread_count,
            last_message_preview: value.last_message_preview,
            participants: value.participant_display_names,
            display_name: value.display_name,
        }
    }
}

impl From<kordophone_db::models::Conversation> for PrintableConversation {
    fn from(value: kordophone_db::models::Conversation) -> Self {
        Self {
            guid: value.guid,
            date: OffsetDateTime::from_unix_timestamp(value.date.and_utc().timestamp()).unwrap(),
            unread_count: value.unread_count.into(),
            last_message_preview: value.last_message_preview,
            participants: value
                .participants
                .into_iter()
                .map(|p| p.display_name())
                .collect(),
            display_name: value.display_name,
        }
    }
}

#[cfg(target_os = "linux")]
impl From<dbus::arg::PropMap> for PrintableConversation {
    fn from(value: dbus::arg::PropMap) -> Self {
        Self {
            guid: value.get("guid").unwrap().as_str().unwrap().to_string(),
            date: OffsetDateTime::from_unix_timestamp(value.get("date").unwrap().as_i64().unwrap())
                .unwrap(),
            unread_count: value
                .get("unread_count")
                .unwrap()
                .as_i64()
                .unwrap()
                .try_into()
                .unwrap(),
            last_message_preview: value
                .get("last_message_preview")
                .unwrap()
                .as_str()
                .map(|s| s.to_string()),
            participants: value
                .get("participants")
                .unwrap()
                .0
                .as_iter()
                .unwrap()
                .map(|s| s.as_str().unwrap().to_string())
                .collect(),
            display_name: value
                .get("display_name")
                .unwrap()
                .as_str()
                .map(|s| s.to_string()),
        }
    }
}

pub struct PrintableMessage {
    pub guid: String,
    pub date: OffsetDateTime,
    pub sender: String,
    pub text: String,
    pub file_transfer_guids: Vec<String>,
    pub attachment_metadata: Option<HashMap<String, AttachmentMetadata>>,
}

impl From<kordophone::model::Message> for PrintableMessage {
    fn from(value: kordophone::model::Message) -> Self {
        Self {
            guid: value.guid,
            date: value.date,
            sender: value.sender.unwrap_or("<me>".to_string()),
            text: value.text,
            file_transfer_guids: value.file_transfer_guids,
            attachment_metadata: value.attachment_metadata,
        }
    }
}

impl From<kordophone_db::models::Message> for PrintableMessage {
    fn from(value: kordophone_db::models::Message) -> Self {
        Self {
            guid: value.id,
            date: OffsetDateTime::from_unix_timestamp(value.date.and_utc().timestamp()).unwrap(),
            sender: value.sender.display_name(),
            text: value.text,
            file_transfer_guids: value.file_transfer_guids,
            attachment_metadata: value.attachment_metadata,
        }
    }
}

#[cfg(target_os = "linux")]
impl From<dbus::arg::PropMap> for PrintableMessage {
    fn from(value: dbus::arg::PropMap) -> Self {
        // Parse file transfer GUIDs from JSON if present
        let file_transfer_guids = value
            .get("file_transfer_guids")
            .and_then(|v| v.as_str())
            .and_then(|json_str| serde_json::from_str(json_str).ok())
            .unwrap_or_default();

        // Parse attachment metadata from JSON if present
        let attachment_metadata = value
            .get("attachment_metadata")
            .and_then(|v| v.as_str())
            .and_then(|json_str| serde_json::from_str(json_str).ok());

        Self {
            guid: value.get("id").unwrap().as_str().unwrap().to_string(),
            date: OffsetDateTime::from_unix_timestamp(value.get("date").unwrap().as_i64().unwrap())
                .unwrap(),
            sender: value.get("sender").unwrap().as_str().unwrap().to_string(),
            text: value.get("text").unwrap().as_str().unwrap().to_string(),
            file_transfer_guids,
            attachment_metadata,
        }
    }
}

pub struct ConversationPrinter<'a> {
    doc: RcDoc<'a, PrintableConversation>,
}

impl<'a> ConversationPrinter<'a> {
    pub fn new(conversation: &'a PrintableConversation) -> Self {
        let preview = conversation
            .last_message_preview
            .as_deref()
            .unwrap_or("<null>")
            .replace('\n', " ");

        let doc = RcDoc::text(format!("<Conversation: \"{}\"", &conversation.guid))
            .append(
                RcDoc::line()
                    .append("Display Name: ")
                    .append(conversation.display_name.as_deref().unwrap_or("<null>"))
                    .append(RcDoc::line())
                    .append("Date: ")
                    .append(conversation.date.to_string())
                    .append(RcDoc::line())
                    .append("Unread Count: ")
                    .append(conversation.unread_count.to_string())
                    .append(RcDoc::line())
                    .append("Participants: ")
                    .append("[")
                    .append(
                        RcDoc::line()
                            .append(
                                conversation
                                    .participants
                                    .iter()
                                    .map(|name| RcDoc::text(name).append(",").append(RcDoc::line()))
                                    .fold(RcDoc::nil(), |acc, x| acc.append(x)),
                            )
                            .nest(4),
                    )
                    .append("]")
                    .append(RcDoc::line())
                    .append("Last Message Preview: ")
                    .append(preview)
                    .nest(4),
            )
            .append(RcDoc::line())
            .append(">");

        ConversationPrinter { doc }
    }
}

impl Display for ConversationPrinter<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.doc.render_fmt(180, f)
    }
}

pub struct MessagePrinter<'a> {
    doc: RcDoc<'a, PrintableMessage>,
}

impl Display for MessagePrinter<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.doc.render_fmt(180, f)
    }
}

impl<'a> MessagePrinter<'a> {
    pub fn new(message: &'a PrintableMessage) -> Self {
        let mut doc = RcDoc::text(format!("<Message: \"{}\"", &message.guid)).append(
            RcDoc::line()
                .append("Date: ")
                .append(message.date.to_string())
                .append(RcDoc::line())
                .append("Sender: ")
                .append(&message.sender)
                .append(RcDoc::line())
                .append("Body: ")
                .append(&message.text)
                .nest(4),
        );

        // Add file transfer GUIDs and attachment metadata if present
        if !message.file_transfer_guids.is_empty() {
            doc = doc.append(RcDoc::line()).append(
                RcDoc::line()
                    .append("Attachments:")
                    .append(
                        message
                            .file_transfer_guids
                            .iter()
                            .map(|guid| {
                                let mut attachment_doc = RcDoc::line().append("- ").append(guid);

                                // Add metadata if available for this GUID
                                if let Some(ref metadata) = message.attachment_metadata {
                                    if let Some(attachment_meta) = metadata.get(guid) {
                                        if let Some(ref attribution) =
                                            attachment_meta.attribution_info
                                        {
                                            if let (Some(width), Some(height)) =
                                                (attribution.width, attribution.height)
                                            {
                                                attachment_doc = attachment_doc
                                                    .append(RcDoc::line())
                                                    .append("    Dimensions: ")
                                                    .append(width.to_string())
                                                    .append(" × ")
                                                    .append(height.to_string());
                                            }
                                        }
                                    }
                                }

                                attachment_doc
                            })
                            .fold(RcDoc::nil(), |acc, x| acc.append(x)),
                    )
                    .nest(4),
            );
        }

        doc = doc.append(RcDoc::line()).append(">");

        MessagePrinter { doc }
    }
}
