use kordophone::api::event_socket::{EventSocket, SocketEvent, SocketUpdate};
use kordophone::api::http_client::Credentials;
use kordophone::api::http_client::HTTPAPIClient;
use kordophone::api::InMemoryAuthenticationStore;
use kordophone::APIInterface;

use crate::printers::{ConversationPrinter, MessagePrinter};
use anyhow::Result;
use clap::Subcommand;
use kordophone::model::event::EventData;
use kordophone::model::outgoing_message::OutgoingMessage;

use futures_util::StreamExt;

pub fn make_api_client_from_env() -> HTTPAPIClient<InMemoryAuthenticationStore> {
    dotenv::dotenv().ok();

    // read from env
    let base_url = std::env::var("KORDOPHONE_API_URL").expect("KORDOPHONE_API_URL must be set");

    let credentials = Credentials {
        username: std::env::var("KORDOPHONE_USERNAME").expect("KORDOPHONE_USERNAME must be set"),

        password: std::env::var("KORDOPHONE_PASSWORD").expect("KORDOPHONE_PASSWORD must be set"),
    };

    HTTPAPIClient::new(
        base_url.parse().unwrap(),
        InMemoryAuthenticationStore::new(Some(credentials)),
    )
}

#[derive(Subcommand)]
pub enum Commands {
    /// Prints all known conversations on the server.
    Conversations,

    /// Prints all messages in a conversation.
    Messages { conversation_id: String },

    /// Prints the server Kordophone version.
    Version,

    /// Prints all events from the server.
    Events,

    /// Prints all raw updates from the server.
    RawUpdates,

    /// Sends a message to the server.
    SendMessage {
        conversation_id: String,
        message: String,
    },

    /// Marks a conversation as read.
    Mark { conversation_id: String },
}

impl Commands {
    pub async fn run(cmd: Commands) -> Result<()> {
        let mut client = ClientCli::new();
        match cmd {
            Commands::Version => client.print_version().await,
            Commands::Conversations => client.print_conversations().await,
            Commands::Messages { conversation_id } => client.print_messages(conversation_id).await,
            Commands::RawUpdates => client.print_raw_updates().await,
            Commands::Events => client.print_events().await,
            Commands::SendMessage {
                conversation_id,
                message,
            } => client.send_message(conversation_id, message).await,
            Commands::Mark { conversation_id } => {
                client.mark_conversation_as_read(conversation_id).await
            }
        }
    }
}

struct ClientCli {
    api: HTTPAPIClient<InMemoryAuthenticationStore>,
}

impl ClientCli {
    pub fn new() -> Self {
        let api = make_api_client_from_env();
        Self { api }
    }

    pub async fn print_version(&mut self) -> Result<()> {
        let version = self.api.get_version().await?;
        println!("Version: {}", version);
        Ok(())
    }

    pub async fn print_conversations(&mut self) -> Result<()> {
        let conversations = self.api.get_conversations().await?;
        for conversation in conversations {
            println!("{}", ConversationPrinter::new(&conversation.into()));
        }

        Ok(())
    }

    pub async fn print_messages(&mut self, conversation_id: String) -> Result<()> {
        let messages = self
            .api
            .get_messages(&conversation_id, None, None, None)
            .await?;
        for message in messages {
            println!("{}", MessagePrinter::new(&message.into()));
        }
        Ok(())
    }

    pub async fn print_events(&mut self) -> Result<()> {
        let socket = self.api.open_event_socket(None).await?;

        let (mut stream, _) = socket.events().await;
        while let Some(Ok(socket_event)) = stream.next().await {
            match socket_event {
                SocketEvent::Update(event) => match event.data {
                    EventData::ConversationChanged(conversation) => {
                        println!("Conversation changed: {}", conversation.guid);
                    }
                    EventData::MessageReceived(conversation, message) => {
                        println!(
                            "Message received: msg: {} conversation: {}",
                            message.guid, conversation.guid
                        );
                    }
                },
                SocketEvent::Pong => {
                    println!("Pong");
                }
            }
        }
        Ok(())
    }

    pub async fn print_raw_updates(&mut self) -> Result<()> {
        let socket = self.api.open_event_socket(None).await?;

        println!("Listening for raw updates...");
        let mut stream = socket.raw_updates().await;

        loop {
            match stream.next().await.unwrap() {
                Ok(update) => {
                    match update {
                        SocketUpdate::Update(updates) => {
                            for update in updates {
                                println!("Got update: {:?}", update);
                            }
                        }
                        SocketUpdate::Pong => {
                            println!("Pong");
                        }
                    }
                },

                Err(e) => {
                    println!("Update error: {:?}", e);
                    break;
                }
            }
        }

        Ok(())
    }

    pub async fn send_message(&mut self, conversation_id: String, message: String) -> Result<()> {
        let outgoing_message = OutgoingMessage::builder()
            .conversation_id(conversation_id)
            .text(message)
            .build();

        let message = self.api.send_message(&outgoing_message).await?;
        println!("Message sent: {}", message.guid);
        Ok(())
    }

    pub async fn mark_conversation_as_read(&mut self, conversation_id: String) -> Result<()> {
        self.api.mark_conversation_as_read(&conversation_id).await?;
        println!("Conversation marked as read: {}", conversation_id);
        Ok(())
    }
}
