use std::error::Error;
use std::io::{self, Read};
use std::path::Path;
use std::time::Duration;

use clap::Parser;

mod api;
mod cli;
mod config;
mod exec;
mod temp;
mod zip;

pub type AppResult<T> = Result<T, Box<dyn Error>>;

pub async fn run() -> AppResult<()> {
    let args = cli::Args::parse();

    if let Some(cli::Command::Login) = args.command {
        return config::handle_login();
    }

    cli::validate_mode_combinations(&args)?;

    let config = config::Config {
        base_url: config::base_url(),
        token: config::get_token()?,
        debug: args.debug,
    };

    if !args.zip.is_empty() {
        let zip = zip::create_zip(&args.zip)?;
        return api::send_file(&config, zip.path(), None, args.parse_mode).await;
    }

    if let Some(command) = args.exec {
        return exec::run_command_and_notify(
            &config,
            &command,
            args.title,
            args.level,
            args.parse_mode,
            args.interval,
        )
        .await;
    }

    let message = args.message.map(Ok).unwrap_or_else(stdin_message)?;

    if let Some(raw_buttons) = args.buttons {
        let buttons = cli::parse_buttons(&raw_buttons)?;
        return api::send_prompt(
            &config,
            message,
            buttons,
            args.parse_mode,
            Duration::from_secs(args.timeout),
        )
        .await;
    }

    let path = Path::new(&message);
    if path.is_file() {
        let caption = cli::file_caption(args.title, args.level);
        return api::send_file(&config, path, caption, args.parse_mode).await;
    }

    api::send_message_or_file(&config, message, args.title, args.level, args.parse_mode).await
}

fn stdin_message() -> AppResult<String> {
    use std::io::IsTerminal;

    if io::stdin().is_terminal() {
        return Err(io::Error::other(
            "No message provided. Usage: tgn <message> or echo <message> | tgn",
        )
        .into());
    }

    let mut buf = String::new();
    io::stdin().read_to_string(&mut buf)?;
    let message = buf.trim_end_matches('\n').to_string();
    if message.is_empty() {
        return Err(io::Error::other("No message provided").into());
    }
    Ok(message)
}
