use anyhow::Result;
use clap::Subcommand;
use kordophone::APIInterface;
use std::{env, path::PathBuf};

use crate::{
    client,
    printers::{ConversationPrinter, MessagePrinter},
};
use kordophone_db::database::{Database, DatabaseAccess};

#[derive(Subcommand)]
pub enum Commands {
    /// For dealing with the table of cached conversations.
    Conversations {
        #[clap(subcommand)]
        command: ConversationCommands,
    },

    /// For dealing with the table of cached messages.
    Messages {
        #[clap(subcommand)]
        command: MessageCommands,
    },

    /// For managing settings in the database.
    Settings {
        #[clap(subcommand)]
        command: SettingsCommands,
    },
}

#[derive(Subcommand)]
pub enum ConversationCommands {
    /// Lists all conversations currently in the database.
    List,

    /// Syncs with an API client.
    Sync,
}

#[derive(Subcommand)]
pub enum MessageCommands {
    /// Prints all messages in a conversation.
    List { conversation_id: String },
}

#[derive(Subcommand)]
pub enum SettingsCommands {
    /// Lists all settings or gets a specific setting.
    Get {
        /// The key to get. If not provided, all settings will be listed.
        key: Option<String>,
    },

    /// Sets a setting value.
    Put {
        /// The key to set.
        key: String,
        /// The value to set.
        value: String,
    },

    /// Deletes a setting.
    Delete {
        /// The key to delete.
        key: String,
    },
}

impl Commands {
    pub async fn run(cmd: Commands) -> Result<()> {
        let mut db = DbClient::new()?;
        match cmd {
            Commands::Conversations { command: cmd } => match cmd {
                ConversationCommands::List => db.print_conversations().await,
                ConversationCommands::Sync => db.sync_with_client().await,
            },
            Commands::Messages { command: cmd } => match cmd {
                MessageCommands::List { conversation_id } => {
                    db.print_messages(&conversation_id).await
                }
            },
            Commands::Settings { command: cmd } => match cmd {
                SettingsCommands::Get { key } => db.get_setting(key).await,
                SettingsCommands::Put { key, value } => db.put_setting(key, value).await,
                SettingsCommands::Delete { key } => db.delete_setting(key).await,
            },
        }
    }
}

struct DbClient {
    database: Database,
}

impl DbClient {
    fn database_path() -> PathBuf {
        env::var("KORDOPHONE_DB_PATH")
            .unwrap_or_else(|_| {
                let temp_dir = env::temp_dir();
                temp_dir.join("kpcli_chat.db").to_str().unwrap().to_string()
            })
            .into()
    }

    pub fn new() -> Result<Self> {
        let path = Self::database_path();
        let path_str: &str = path.as_path().to_str().unwrap();

        println!("kpcli: Using db at {}", path_str);

        let db = Database::new(path_str)?;
        Ok(Self { database: db })
    }

    pub async fn print_conversations(&mut self) -> Result<()> {
        let all_conversations = self
            .database
            .with_repository(|repository| repository.all_conversations(i32::MAX, 0))
            .await?;

        println!("{} Conversations: ", all_conversations.len());
        for conversation in all_conversations {
            println!("{}", ConversationPrinter::new(&conversation.into()));
        }

        Ok(())
    }

    pub async fn print_messages(&mut self, conversation_id: &str) -> Result<()> {
        let messages = self
            .database
            .with_repository(|repository| repository.get_messages_for_conversation(conversation_id))
            .await?;

        for message in messages {
            println!("{}", MessagePrinter::new(&message.into()));
        }
        Ok(())
    }

    pub async fn sync_with_client(&mut self) -> Result<()> {
        let mut client = client::make_api_client_from_env();
        let fetched_conversations = client.get_conversations().await?;
        let db_conversations: Vec<kordophone_db::models::Conversation> = fetched_conversations
            .into_iter()
            .map(kordophone_db::models::Conversation::from)
            .collect();

        // Process each conversation
        for conversation in db_conversations {
            let conversation_id = conversation.guid.clone();

            // Insert the conversation
            self.database
                .with_repository(|repository| repository.insert_conversation(conversation))
                .await?;

            // Fetch and sync messages for this conversation
            let messages = client
                .get_messages(&conversation_id, None, None, None)
                .await?;
            let db_messages: Vec<kordophone_db::models::Message> = messages
                .into_iter()
                .map(kordophone_db::models::Message::from)
                .collect();

            // Insert each message
            self.database
                .with_repository(|repository| -> Result<()> {
                    for message in db_messages {
                        repository.insert_message(&conversation_id, message)?;
                    }

                    Ok(())
                })
                .await?;
        }

        Ok(())
    }

    pub async fn get_setting(&mut self, key: Option<String>) -> Result<()> {
        self.database
            .with_settings(|settings| {
                match key {
                    Some(key) => {
                        // Get a specific setting
                        let value: Option<String> = settings.get(&key)?;
                        match value {
                            Some(v) => println!("{} = {}", key, v),
                            None => println!("Setting '{}' not found", key),
                        }
                    }
                    None => {
                        // List all settings
                        let keys = settings.list_keys()?;
                        if keys.is_empty() {
                            println!("No settings found");
                        } else {
                            println!("Settings:");
                            for key in keys {
                                let value: Option<String> = settings.get(&key)?;
                                match value {
                                    Some(v) => println!("  {} = {}", key, v),
                                    None => println!("  {} = <error reading value>", key),
                                }
                            }
                        }
                    }
                }

                Ok(())
            })
            .await
    }

    pub async fn put_setting(&mut self, key: String, value: String) -> Result<()> {
        self.database
            .with_settings(|settings| {
                settings.put(&key, &value)?;
                Ok(())
            })
            .await
    }

    pub async fn delete_setting(&mut self, key: String) -> Result<()> {
        self.database
            .with_settings(|settings| {
                let count = settings.del(&key)?;
                if count == 0 {
                    println!("Setting '{}' not found", key);
                }
                Ok(())
            })
            .await
    }
}
