use std::io::{IsTerminal as _, Read as _};
use std::process::ExitCode;
use std::sync::{Arc, Mutex};

use clap::Parser;

fn main() -> ExitCode {
    let cli = match bds_cli::Cli::try_parse() {
        Ok(cli) => cli,
        Err(error) => {
            let success = matches!(
                error.kind(),
                clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion
            );
            let _ = error.print();
            return if success {
                ExitCode::SUCCESS
            } else {
                ExitCode::from(1)
            };
        }
    };

    if let bds_cli::Command::Server(args) = &cli.command {
        let data_root = args
            .data_dir
            .clone()
            .unwrap_or_else(bds_core::util::application_data_dir);
        let database_path = args
            .database
            .clone()
            .unwrap_or_else(|| data_root.join("bds.db"));
        let config = match bds_server::ServerConfig::from_environment(database_path, data_root) {
            Ok(mut config) => {
                if let Some(bind) = args.bind {
                    config.bind = bind;
                }
                if let Some(port) = args.port {
                    config.port = port;
                }
                config
            }
            Err(error) => {
                eprintln!("Error: {error:#}");
                return ExitCode::from(1);
            }
        };
        return run_headless(bds_server::boot::BootMode::Server, config);
    }

    if matches!(cli.command, bds_cli::Command::Tui) {
        let data_root = bds_core::util::application_data_dir();
        let config = match bds_server::ServerConfig::from_environment(
            bds_core::util::application_database_path(),
            data_root,
        ) {
            Ok(config) => config,
            Err(error) => {
                eprintln!("Error: {error:#}");
                return ExitCode::from(1);
            }
        };
        return run_headless(bds_server::boot::BootMode::Tui, config);
    }

    let json = cli.json;
    let needs_stdin = match &cli.command {
        bds_cli::Command::Post(args) => args.stdin,
        bds_cli::Command::Gallery(args) => args.post.stdin,
        _ => false,
    };
    let mut context = bds_cli::RunContext::system();
    if needs_stdin && let Err(error) = std::io::stdin().read_to_string(&mut context.stdin) {
        eprintln!("Error: could not read stdin: {error}");
        return ExitCode::from(1);
    }

    let progress = (!json).then(|| {
        Arc::new(Mutex::new(bds_cli::HumanProgress::new(
            std::io::stdout().is_terminal(),
        )))
    });
    let sink = progress.as_ref().map(|progress| {
        let progress = Arc::clone(progress);
        Arc::new(move |update: bds_cli::CliProgress| {
            let _ = progress
                .lock()
                .unwrap_or_else(|poisoned| poisoned.into_inner())
                .write(&update, std::io::stdout().lock());
        }) as bds_cli::ProgressSink
    });
    let result = bds_cli::run_with_progress(cli, context, sink);
    if let Some(progress) = &progress {
        let _ = progress
            .lock()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
            .finish(std::io::stdout().lock());
    }

    match result {
        Ok(mut output) => {
            if !json {
                output.progress.clear();
            }
            println!("{output}");
            ExitCode::SUCCESS
        }
        Err(error) => {
            if json {
                eprintln!(
                    "{}",
                    serde_json::json!({"ok": false, "error": format!("{error:#}")})
                );
            } else {
                eprintln!("Error: {error:#}");
            }
            ExitCode::from(1)
        }
    }
}

fn run_headless(mode: bds_server::boot::BootMode, config: bds_server::ServerConfig) -> ExitCode {
    match bds_server::run_headless(mode, config) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("Error: {error:#}");
            ExitCode::from(1)
        }
    }
}
