use std::io::{stdout, Write};

use clap::{command, Parser};
use futures::StreamExt;
use multilink::{
    http::client::HttpClientConfig, stdio::client::StdioClientConfig,
    util::service::build_service_from_config, ServiceResponse,
};
use protocol::{GreetingResponse, Request, Response, SayCustomGreetingRequest, SayHelloRequest};
use tracing_subscriber::{filter::LevelFilter, EnvFilter};

use crate::protocol::GreetingStreamResponse;

mod protocol;

const SERVER_STDIO_COMMAND: &str = "greeting-server";
const SERVER_STDIO_COMMAND_ARGS: [&'static str; 1] = ["stdio-server"];

/// A client for the greeting server.
#[derive(Parser, Debug)]
#[command(about)]
struct Cli {
    /// Binary path for invoking the stdio server.
    #[arg(long, default_value = "./target/debug/examples")]
    stdio_bin_path: Option<String>,

    /// The HTTP base URL for sending requests to the http server.
    #[arg(long, default_value = "http://localhost:8080")]
    http_base_url: String,

    /// Send requests to HTTP server instead of invoking stdio server.
    #[arg(long, default_value_t = false)]
    use_http: bool,

    /// Custom greeting prefix that the server should use.
    #[arg(long)]
    custom_greeting: Option<String>,

    /// Stream each character of the greeting from the server. Custom greetings not supported.
    #[arg(long)]
    stream_hello: bool,

    /// The name of the person that the greeting should greet.
    #[arg(long, default_value = "Bob")]
    name: String,
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::builder()
                .with_default_directive(LevelFilter::INFO.into())
                .from_env()
                .unwrap(),
        )
        .init();

    let cli = Cli::parse();

    let stdio_config = Some(StdioClientConfig {
        bin_path: cli.stdio_bin_path,
        ..Default::default()
    });

    let http_config = match cli.use_http {
        true => Some(HttpClientConfig {
            base_url: cli.http_base_url,
            ..Default::default()
        }),
        false => None,
    };

    let mut client_service = build_service_from_config::<Request, Response>(
        SERVER_STDIO_COMMAND,
        &SERVER_STDIO_COMMAND_ARGS,
        stdio_config,
        http_config,
    )
    .await
    .expect("should be able to create client service");

    let request = match cli.custom_greeting {
        Some(greeting) => Request::SayCustomGreeting(SayCustomGreetingRequest {
            name: cli.name,
            greeting,
        }),
        None => {
            let request = SayHelloRequest { name: cli.name };
            match cli.stream_hello {
                false => Request::SayHello(request),
                true => Request::SayHelloStream(request),
            }
        }
    };

    let response = client_service
        .call(request)
        .await
        .expect("client request should succeed");

    match response {
        ServiceResponse::Single(response) => {
            let result = match response {
                Response::SayCustomGreeting(GreetingResponse { result }) => result,
                Response::SayHello(GreetingResponse { result }) => result,
                _ => panic!("response type invalid for single response"),
            };
            println!("Server says: {}", result);
        }
        ServiceResponse::Multiple(mut response_stream) => {
            let mut stdout = stdout();
            print!("Server says: ");
            stdout.flush().unwrap();
            while let Some(result) = response_stream.next().await {
                match result.expect("client stream request should succeed") {
                    Response::SayHelloStream(GreetingStreamResponse { character }) => {
                        print!("{character}");
                        stdout.flush().unwrap();
                    }
                    _ => panic!("response type invalid for streaming response"),
                }
            }
            println!();
        }
    }
}