//! Linux-only D-Bus implementation of the `DaemonInterface`.
#![cfg(target_os = "linux")]

use super::{ConfigCommands, DaemonInterface};
use crate::printers::{ConversationPrinter, MessagePrinter};
use anyhow::Result;
use async_trait::async_trait;
use dbus::blocking::{Connection, Proxy};
use prettytable::table;

const DBUS_NAME: &str = "net.buzzert.kordophonecd";
const DBUS_PATH: &str = "/net/buzzert/kordophonecd/daemon";

#[allow(unused)]
mod dbus_interface {
    #![allow(unused)]
    include!(concat!(env!("OUT_DIR"), "/kordophone-client.rs"));
}

use dbus_interface::NetBuzzertKordophoneRepository as KordophoneRepository;
use dbus_interface::NetBuzzertKordophoneSettings as KordophoneSettings;

pub struct DBusDaemonInterface {
    conn: Connection,
}

impl DBusDaemonInterface {
    pub fn new() -> Result<Self> {
        Ok(Self {
            conn: Connection::new_session()?,
        })
    }

    fn proxy(&self) -> Proxy<&Connection> {
        self.conn
            .with_proxy(DBUS_NAME, DBUS_PATH, std::time::Duration::from_secs(30))
    }

    async fn print_settings(&mut self) -> Result<()> {
        let server_url = KordophoneSettings::server_url(&self.proxy()).unwrap_or_default();
        let username = KordophoneSettings::username(&self.proxy()).unwrap_or_default();

        let table = table!([
            b->"Server URL", &server_url
        ], [
            b->"Username", &username
        ]);
        table.printstd();
        Ok(())
    }

    async fn set_server_url(&mut self, url: String) -> Result<()> {
        KordophoneSettings::set_server_url(&self.proxy(), url)
            .map_err(|e| anyhow::anyhow!("Failed to set server URL: {}", e))
    }

    async fn set_username(&mut self, username: String) -> Result<()> {
        KordophoneSettings::set_username(&self.proxy(), username)
            .map_err(|e| anyhow::anyhow!("Failed to set username: {}", e))
    }
}

#[async_trait]
impl DaemonInterface for DBusDaemonInterface {
    async fn print_version(&mut self) -> Result<()> {
        let version = KordophoneRepository::get_version(&self.proxy())?;
        println!("Server version: {}", version);
        Ok(())
    }

    async fn print_conversations(&mut self) -> Result<()> {
        let conversations = KordophoneRepository::get_conversations(&self.proxy(), 100, 0)?;
        println!("Number of conversations: {}", conversations.len());
        for conversation in conversations {
            println!("{}", ConversationPrinter::new(&conversation.into()));
        }
        Ok(())
    }

    async fn sync_conversations(&mut self, conversation_id: Option<String>) -> Result<()> {
        if let Some(conversation_id) = conversation_id {
            KordophoneRepository::sync_conversation(&self.proxy(), &conversation_id)
                .map_err(|e| anyhow::anyhow!("Failed to sync conversation: {}", e))
        } else {
            KordophoneRepository::sync_all_conversations(&self.proxy())
                .map_err(|e| anyhow::anyhow!("Failed to sync conversations: {}", e))
        }
    }

    async fn sync_conversations_list(&mut self) -> Result<()> {
        KordophoneRepository::sync_conversation_list(&self.proxy())
            .map_err(|e| anyhow::anyhow!("Failed to sync conversations: {}", e))
    }

    async fn print_messages(
        &mut self,
        conversation_id: String,
        last_message_id: Option<String>,
    ) -> Result<()> {
        let messages = KordophoneRepository::get_messages(
            &self.proxy(),
            &conversation_id,
            &last_message_id.unwrap_or_default(),
        )?;
        println!("Number of messages: {}", messages.len());
        for message in messages {
            println!("{}", MessagePrinter::new(&message.into()));
        }
        Ok(())
    }

    async fn enqueue_outgoing_message(
        &mut self,
        conversation_id: String,
        text: String,
    ) -> Result<()> {
        let attachment_guids: Vec<&str> = vec![];
        let outgoing_message_id = KordophoneRepository::send_message(
            &self.proxy(),
            &conversation_id,
            &text,
            attachment_guids,
        )?;
        println!("Outgoing message ID: {}", outgoing_message_id);
        Ok(())
    }

    async fn wait_for_signals(&mut self) -> Result<()> {
        use dbus::Message;
        mod dbus_signals {
            pub use super::dbus_interface::NetBuzzertKordophoneRepositoryConversationsUpdated as ConversationsUpdated;
        }

        let _id = self.proxy().match_signal(
            |_: dbus_signals::ConversationsUpdated, _: &Connection, _: &Message| {
                println!("Signal: Conversations updated");
                true
            },
        );

        println!("Waiting for signals...");
        loop {
            self.conn.process(std::time::Duration::from_millis(1000))?;
        }
    }

    async fn config(&mut self, cmd: ConfigCommands) -> Result<()> {
        match cmd {
            ConfigCommands::Print => self.print_settings().await,
            ConfigCommands::SetServerUrl { url } => self.set_server_url(url).await,
            ConfigCommands::SetUsername { username } => self.set_username(username).await,
        }
    }

    async fn delete_all_conversations(&mut self) -> Result<()> {
        KordophoneRepository::delete_all_conversations(&self.proxy())
            .map_err(|e| anyhow::anyhow!("Failed to delete all conversations: {}", e))
    }

    async fn download_attachment(&mut self, attachment_id: String) -> Result<()> {
        // Trigger download.
        KordophoneRepository::download_attachment(&self.proxy(), &attachment_id, false)?;

        // Get attachment info.
        let attachment_info =
            KordophoneRepository::get_attachment_info(&self.proxy(), &attachment_id)?;
        let (path, _preview_path, downloaded, _preview_downloaded) = attachment_info;

        if downloaded {
            println!("Attachment already downloaded: {}", path);
            return Ok(());
        }

        println!("Downloading attachment: {}", attachment_id);

        // Attach to the signal that the attachment has been downloaded.
        let download_path = path.clone();
        let _id = self.proxy().match_signal(
            move |_: dbus_interface::NetBuzzertKordophoneRepositoryAttachmentDownloadCompleted,
                  _: &Connection,
                  _: &dbus::message::Message| {
                println!("Signal: Attachment downloaded: {}", download_path);
                std::process::exit(0);
            },
        );

        let _id = self.proxy().match_signal(
            |h: dbus_interface::NetBuzzertKordophoneRepositoryAttachmentDownloadFailed,
             _: &Connection,
             _: &dbus::message::Message| {
                println!("Signal: Attachment download failed: {}", h.attachment_id);
                std::process::exit(1);
            },
        );

        // Wait for the signal.
        loop {
            self.conn.process(std::time::Duration::from_millis(1000))?;
        }
    }

    async fn upload_attachment(&mut self, path: String) -> Result<()> {
        let upload_guid = KordophoneRepository::upload_attachment(&self.proxy(), &path)?;
        println!("Upload GUID: {}", upload_guid);
        Ok(())
    }

    async fn mark_conversation_as_read(&mut self, conversation_id: String) -> Result<()> {
        KordophoneRepository::mark_conversation_as_read(&self.proxy(), &conversation_id)
            .map_err(|e| anyhow::anyhow!("Failed to mark conversation as read: {}", e))
    }
}
