use std::path::PathBuf;

use anyhow::Context;
use anyhow::Result;

use sequoia_ipc as ipc;

use sequoia_keystore as keystore;

use clap::Parser;
use clap::ValueEnum;

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
enum Ephemeral {
    /// Enable ephemeral mode.
    True,
    /// Disable ephemeral mode.
    False,
}

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    #[clap(
        long,
        env = "SEQUOIA_HOME",
        help = "Set the home directory",
        long_help = format!("\
Set the home directory.

Sequoia's default home directory is `{}`.  When using the default \
location, files are placed according to the local standard, \
e.g., the XDG Base Directory Specification.  When an alternate \
location is specified, the user data, configuration files, and \
cache data are placed under a single, unified directory.  This is \
a lightweight way to partially isolate programs.",
            sequoia_directories::Home::default_location()
                .map(|p| {
                    let p = p.display().to_string();
                    if let Some(home) = dirs::home_dir() {
                        let home = home.display().to_string();
                        if let Some(rest) = p.strip_prefix(&home) {
                            return format!("$HOME{}", rest);
                        }
                    }
                    p
                })
                .unwrap_or("<unknown>".to_string())),
    )]
    pub sequoia_home: Option<PathBuf>,

    #[clap(
        long,
        help = "Set the keystore's home directory",
        long_help = format!("\
Set the component's home directory.

Sequoia's default home directory is `{}`.  The keystore's \
default home directory is `{}`.  If this option is set, this \
overrides `--sequoia-home`.",
            sequoia_directories::Home::default_location()
                .map(|p| {
                    let p = p.display().to_string();
                    if let Some(home) = dirs::home_dir() {
                        let home = home.display().to_string();
                        if let Some(rest) = p.strip_prefix(&home) {
                            return format!("$HOME{}", rest);
                        }
                    }
                    p
                })
                .unwrap_or("<unknown>".to_string()),
            sequoia_directories::Home::default()
                .map(|home| {
                    home.data_dir(sequoia_directories::Component::Keystore)
                })
                .map(|p| {
                    let p = p.display().to_string();
                    if let Some(home) = dirs::home_dir() {
                        let home = home.display().to_string();
                        if let Some(rest) = p.strip_prefix(&home) {
                            return format!("$HOME{}", rest);
                        }
                    }
                    p
                })
                .unwrap_or("<unknown>".to_string())),
    )]
    pub home: Option<PathBuf>,

    #[clap(
        hide = true,
        long,
        value_enum,
        help = "Whether to create an ephemeral context",
        long_help = "\
Whether to create an ephemeral context.

An ephemeral context is destroyed when the program exists.
",
    )]
    pub ephemeral: Option<Ephemeral>,

    #[clap(
        hide = true,
        long,
        help = "Set the directory containing the backend servers",
    )]
    pub lib: Option<PathBuf>,

    #[clap(
        long,
        help = "The socket is passed on the given file descriptor",
        long_help = "\
The socket is passed on the given file descriptor.

This option is normally only used by programs starting the server.",
    )]
    pub socket: Option<usize>,
}

fn main() -> Result<()> {
    let args = Args::parse();

    let keystore_home = if let Some(keystore_home) = args.home {
        keystore_home
    } else {
        // Our home directory.  This respects `SEQUOIA_HOME`.
        let home = sequoia_directories::Home::new(args.sequoia_home)?;
        home.data_dir(sequoia_directories::Component::Keystore)
    };

    std::fs::create_dir_all(&keystore_home)
        .context(format!("Creating {}", keystore_home.display()))?;

    let mut config = keystore::Context::configure()
        .home(&keystore_home);
    if args.ephemeral == Some(Ephemeral::True) {
        config.set_ephemeral();
    }
    if let Some(path) = args.lib {
        config.set_lib(path);
    }
    let context = config.build()?;

    let mut desc = keystore::descriptor(&context);

    if let Some(fd) = args.socket {
        // The server socket is passed on the specified file
        // descriptor.
        if fd != 0 {
            eprintln!("Currently the socket can only be passed on fd 0");
            std::process::exit(1);
        }

        let mut server = ipc::Server::new(desc)?;

        // This blocks the current thread.
        server.serve()?;
    } else {
        // Bootstrap.
        let join_handle = desc.bootstrap()?;

        if let Some(join_handle) = join_handle {
            match join_handle.join() {
                // Server thread panicked.
                Err(err) => panic!("The server thread panicked: {:?}", err),
                // Server thread returned an error.
                Ok(Err(err)) => return Err(err),
                // Server thread exited normally.
                Ok(Ok(())) => (),
            }
        }
    }

    Ok(())
}
