use super::contact_resolver::{ContactResolver, DefaultContactResolverBackend};
use super::models::message::Participant;
use super::{target, Message};

use kordophone_db::{
    database::{Database, DatabaseAccess},
    models::Conversation,
    models::Participant as DbParticipant,
};
use notify::Notification;
use std::sync::Arc;
use tokio::sync::Mutex;

/// Centralised notification helper used by platform transports (D-Bus, XPC, …).
pub struct NotificationService {
    resolver: Mutex<ContactResolver<DefaultContactResolverBackend>>,
}

impl NotificationService {
    pub fn new() -> Self {
        Self {
            resolver: Mutex::new(ContactResolver::new(
                DefaultContactResolverBackend::default(),
            )),
        }
    }

    /// Checks whether a new user-visible notification should be shown for the
    /// given conversation and displays it if appropriate.
    pub async fn notify_new_messages(
        &self,
        database: &Arc<Mutex<Database>>,
        conversation_id: &str,
    ) {
        if let Some((summary, body)) = self.prepare_payload(database, conversation_id).await {
            if let Err(error) = self.show_notification(&summary, &body) {
                log::warn!(
                    target: target::DAEMON,
                    "Failed to display notification for conversation {}: {}",
                    conversation_id,
                    error
                );
            }
        }
    }

    /// Displays a manual test notification.
    pub fn send_manual(&self, summary: &str, body: &str) -> Result<(), notify::error::Error> {
        self.show_notification(summary, body)
    }

    async fn prepare_payload(
        &self,
        database: &Arc<Mutex<Database>>,
        conversation_id: &str,
    ) -> Option<(String, String)> {
        let conversation_opt = database
            .lock()
            .await
            .with_repository(|r| r.get_conversation_by_guid(conversation_id))
            .await;

        let conversation = match conversation_opt {
            Ok(Some(conv)) => conv,
            Ok(None) => return None,
            Err(err) => {
                log::warn!(
                    target: target::DAEMON,
                    "Notification lookup failed for conversation {}: {}",
                    conversation_id,
                    err
                );
                return None;
            }
        };

        if conversation.unread_count == 0 {
            return None;
        }

        let last_message_opt = database
            .lock()
            .await
            .with_repository(|r| r.get_last_message_for_conversation(conversation_id))
            .await;

        let last_message: Message = match last_message_opt {
            Ok(Some(message)) => message.into(),
            Ok(None) => return None,
            Err(err) => {
                log::warn!(
                    target: target::DAEMON,
                    "Notification lookup failed for conversation {}: {}",
                    conversation_id,
                    err
                );
                return None;
            }
        };

        if matches!(last_message.sender, Participant::Me) {
            return None;
        }

        let mut resolver = self.resolver.lock().await;
        let summary = self.conversation_display_name(&conversation, &mut resolver);
        let sender_display_name =
            self.resolve_participant_display_name(&last_message.sender, &mut resolver);

        let mut message_text = last_message.text.replace('\u{FFFC}', "");
        if message_text.trim().is_empty() {
            if !last_message.attachments.is_empty() {
                message_text = "Sent an attachment".to_string();
            } else {
                message_text = "Sent a message".to_string();
            }
        }

        let body = if sender_display_name.is_empty() {
            message_text
        } else {
            format!("{}: {}", sender_display_name, message_text)
        };

        Some((summary, body))
    }

    fn conversation_display_name(
        &self,
        conversation: &Conversation,
        resolver: &mut ContactResolver<DefaultContactResolverBackend>,
    ) -> String {
        if let Some(display_name) = &conversation.display_name {
            if !display_name.trim().is_empty() {
                return display_name.clone();
            }
        }

        let names: Vec<String> = conversation
            .participants
            .iter()
            .filter_map(|participant| match participant {
                DbParticipant::Me => None,
                DbParticipant::Remote { handle, contact_id } => {
                    if let Some(contact_id) = contact_id {
                        Some(
                            resolver
                                .get_contact_display_name(contact_id)
                                .unwrap_or_else(|| handle.clone()),
                        )
                    } else {
                        Some(handle.clone())
                    }
                }
            })
            .collect();

        if names.is_empty() {
            "Kordophone".to_string()
        } else {
            names.join(", ")
        }
    }

    fn resolve_participant_display_name(
        &self,
        participant: &Participant,
        resolver: &mut ContactResolver<DefaultContactResolverBackend>,
    ) -> String {
        match participant {
            Participant::Me => "".to_string(),
            Participant::Remote { handle, contact_id } => {
                if let Some(contact_id) = contact_id {
                    resolver
                        .get_contact_display_name(contact_id)
                        .unwrap_or_else(|| handle.clone())
                } else {
                    handle.clone()
                }
            }
        }
    }

    fn show_notification(&self, summary: &str, body: &str) -> Result<(), notify::error::Error> {
        Notification::new()
            .appname("Kordophone")
            .summary(summary)
            .body(body)
            .show()
            .map(|_| ())
    }
}
