// Hand-rolled CLI parser. Matches the upstream rustdesk style (see
// `src/core_main.rs` in the rustdesk crate) — clap is not pulled into the
// main path. Only a handful of flags are supported on purpose: the surface
// area is the user-facing contract.

use anyhow::{bail, Result};

#[derive(Debug, PartialEq, Eq)]
pub enum Action {
    /// `--install`. Optionally combined with `--config <BLOB>` for MDM
    /// one-liner deployment.
    Install,
    /// `--uninstall`. Stops the service, deletes it, removes config dir.
    Uninstall,
    /// `--service`. SCM entry point; user code should never invoke this
    /// manually except via the service dispatcher.
    Service,
    /// `--server`. Worker mode launched into the active console session by
    /// the service shell.
    Server,
    /// `--config <BLOB>` without `--install`. Persist config and exit.
    ConfigOnly,
    /// No flags. Foreground dev mode.
    None,
    /// `--cm`. Connection-manager process. Spawned as a USER-token child of
    /// the SYSTEM-token `--server` worker (either pre-emptively by hello-agent's
    /// own `spawn_cm_into_user_desktop`, or as a fallback by librustdesk's
    /// `run_as_user` when its first `ipc::connect("_cm")` fails). Binds the
    /// `_cm` named pipe, runs upstream's `IpcTaskRunner` for each incoming
    /// `--server` connection, and lives for as long as the user session does
    /// — every `Data::FS(...)` frame the server sends is executed here, in
    /// the user's security context.
    Cm,
    /// `--update`. Self-replacement entry point launched as an elevated child
    /// by the running service's updater (see `librustdesk::updater`) after it
    /// has downloaded and SHA256-verified a new hello-agent.exe from the
    /// Gitea releases page. `current_exe()` here points at the staged new
    /// binary in `%TEMP%`; it copies itself over the installed location and
    /// restarts the service via `librustdesk::platform::update_me`.
    Update,
}

#[derive(Debug)]
pub struct ParsedArgs {
    pub action: Action,
    pub config_blob: Option<String>,
}

impl ParsedArgs {
    pub fn from_argv<I: IntoIterator<Item = String>>(argv: I) -> Result<Self> {
        let args: Vec<String> = argv.into_iter().collect();
        let mut install = false;
        let mut uninstall = false;
        let mut service = false;
        let mut server = false;
        let mut cm = false;
        let mut update = false;
        let mut config_blob: Option<String> = None;

        let mut i = 0;
        while i < args.len() {
            match args[i].as_str() {
                "--install" => install = true,
                "--uninstall" => uninstall = true,
                "--service" => service = true,
                "--server" => server = true,
                "--update" => update = true,
                // Connection-manager popup mode. Treat `--cm-no-ui` (the
                // Linux-headless variant librustdesk also tries) as a
                // synonym; either way we run cm_popup.
                "--cm" | "--cm-no-ui" => cm = true,
                "--config" => {
                    let next = args.get(i + 1).cloned().ok_or_else(|| {
                        anyhow::anyhow!("--config requires a value")
                    })?;
                    config_blob = Some(next);
                    i += 1;
                }
                "--help" | "-h" => {
                    print_usage();
                    std::process::exit(0);
                }
                "--version" | "-V" => {
                    println!("hello-agent {}", env!("CARGO_PKG_VERSION"));
                    std::process::exit(0);
                }
                other => bail!("unknown argument: {}", other),
            }
            i += 1;
        }

        // Mutual-exclusion rules. --install + --config is the MDM one-liner;
        // everything else is one-action-at-a-time. --update is launched by
        // the updater as a standalone elevated child, never combined.
        let exclusive = [uninstall, service, server, cm, update]
            .iter()
            .filter(|x| **x)
            .count();
        if exclusive > 1 {
            bail!("--uninstall, --service, --server, --cm, --update are mutually exclusive");
        }
        if uninstall && (install || config_blob.is_some()) {
            bail!("--uninstall cannot be combined with other flags");
        }
        if update && (install || config_blob.is_some()) {
            bail!("--update cannot be combined with other flags");
        }

        let action = if uninstall {
            Action::Uninstall
        } else if install {
            Action::Install
        } else if service {
            Action::Service
        } else if server {
            Action::Server
        } else if cm {
            Action::Cm
        } else if update {
            Action::Update
        } else if config_blob.is_some() {
            Action::ConfigOnly
        } else {
            Action::None
        };

        Ok(ParsedArgs {
            action,
            config_blob,
        })
    }
}

pub fn print_usage() {
    eprintln!(
        "hello-agent — headless RustDesk-protocol-compatible support agent

USAGE:
    hello-agent [OPTIONS]

OPTIONS:
    --install              Register and start the Windows service.
    --uninstall            Stop, delete, and clean up the Windows service.
    --config <BLOB>        Import an admin-UI deploy blob. Accepts either the
                           reversed-base64 string emitted by the rustdesk-server
                           admin UI or the `host=...,key=...,api=...,relay=...`
                           filename form. May be combined with --install for
                           one-line MDM deployment.
    --service              SCM entry point. Do not invoke manually.
    --server               Worker mode (launched by the service shell into
                           the active console session).
    --update               Self-replacement entry point. Launched by the
                           running service's updater after downloading and
                           SHA256-verifying a new release from Gitea. Do
                           not invoke manually.
    -h, --help             Show this help.
    -V, --version          Show version.

EXAMPLES:
    hello-agent.exe --install --config 0nI900VsFHZ...
    hello-agent.exe --uninstall
"
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    fn parse(s: &[&str]) -> Result<ParsedArgs> {
        ParsedArgs::from_argv(s.iter().map(|s| s.to_string()))
    }

    #[test]
    fn no_args_is_none() {
        assert_eq!(parse(&[]).unwrap().action, Action::None);
    }

    #[test]
    fn install_with_config() {
        let p = parse(&["--install", "--config", "BLOB"]).unwrap();
        assert_eq!(p.action, Action::Install);
        assert_eq!(p.config_blob.as_deref(), Some("BLOB"));
    }

    #[test]
    fn config_only() {
        let p = parse(&["--config", "BLOB"]).unwrap();
        assert_eq!(p.action, Action::ConfigOnly);
    }

    #[test]
    fn uninstall_alone() {
        assert_eq!(parse(&["--uninstall"]).unwrap().action, Action::Uninstall);
    }

    #[test]
    fn install_uninstall_conflict() {
        assert!(parse(&["--install", "--uninstall"]).is_err());
    }

    #[test]
    fn service_server_conflict() {
        assert!(parse(&["--service", "--server"]).is_err());
    }

    #[test]
    fn config_missing_value() {
        assert!(parse(&["--config"]).is_err());
    }

    #[test]
    fn unknown_arg() {
        assert!(parse(&["--no-such-flag"]).is_err());
    }

    #[test]
    fn update_alone() {
        assert_eq!(parse(&["--update"]).unwrap().action, Action::Update);
    }

    #[test]
    fn update_install_conflict() {
        assert!(parse(&["--update", "--install"]).is_err());
    }

    #[test]
    fn update_service_conflict() {
        assert!(parse(&["--update", "--service"]).is_err());
    }

    #[test]
    fn update_config_conflict() {
        assert!(parse(&["--update", "--config", "BLOB"]).is_err());
    }
}
