use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

use anyhow::{Context, Result, anyhow, bail};
use russh::keys;
use russh::keys::PrivateKey;
use russh::keys::ssh_key::{self, AuthorizedKeys};
use russh::server::Config as RusshServerConfig;
use russh::{MethodKind, MethodSet};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SshAuthMode {
    Key,
    Password,
    Both,
}

impl SshAuthMode {
    pub fn parse(value: &str) -> Option<Self> {
        match value.trim().to_ascii_lowercase().as_str() {
            "key" | "publickey" => Some(Self::Key),
            "password" => Some(Self::Password),
            "both" => Some(Self::Both),
            _ => None,
        }
    }

    pub fn as_str(self) -> &'static str {
        match self {
            Self::Key => "key",
            Self::Password => "password",
            Self::Both => "both",
        }
    }
}

#[derive(Debug, Clone)]
pub struct RtrSshRuntimeConfig {
    pub server_config: Arc<RusshServerConfig>,
    pub authorized_keys: Arc<Vec<ssh_key::PublicKey>>,
    pub username: Arc<str>,
    pub subsystem_name: Arc<str>,
    pub password: Option<Arc<str>>,
}

pub fn load_rtr_ssh_runtime_config(
    host_key_path: impl AsRef<Path>,
    authorized_keys_path: impl AsRef<Path>,
    username: &str,
    subsystem_name: &str,
    auth_mode: SshAuthMode,
    password: Option<&str>,
    inactivity_timeout: Option<Duration>,
    keepalive_interval: Option<Duration>,
) -> Result<RtrSshRuntimeConfig> {
    if username.trim().is_empty() {
        bail!("SSH username must not be empty");
    }
    if subsystem_name.trim().is_empty() {
        bail!("SSH subsystem name must not be empty");
    }

    let host_key = load_host_key(host_key_path.as_ref())?;
    let authorized_keys = if matches!(auth_mode, SshAuthMode::Key | SshAuthMode::Both) {
        load_authorized_keys(authorized_keys_path.as_ref())?
    } else {
        Vec::new()
    };
    let password = password.map(str::trim).filter(|value| !value.is_empty());
    if matches!(auth_mode, SshAuthMode::Password | SshAuthMode::Both) && password.is_none() {
        bail!("SSH auth mode '{}' requires non-empty password", auth_mode.as_str());
    }

    let mut methods = MethodSet::empty();
    if matches!(auth_mode, SshAuthMode::Key | SshAuthMode::Both) {
        methods.push(MethodKind::PublicKey);
    }
    if matches!(auth_mode, SshAuthMode::Password | SshAuthMode::Both) {
        methods.push(MethodKind::Password);
    }

    let server_config = RusshServerConfig {
        methods,
        keys: vec![host_key],
        inactivity_timeout,
        keepalive_interval,
        keepalive_max: 3,
        auth_rejection_time: Duration::from_secs(1),
        auth_rejection_time_initial: Some(Duration::from_secs(0)),
        ..Default::default()
    };

    Ok(RtrSshRuntimeConfig {
        server_config: Arc::new(server_config),
        authorized_keys: Arc::new(authorized_keys),
        username: Arc::from(username.trim()),
        subsystem_name: Arc::from(subsystem_name.trim()),
        password: password.map(Arc::from),
    })
}

fn load_host_key(path: &Path) -> Result<PrivateKey> {
    keys::load_secret_key(path, None).with_context(|| {
        format!(
            "failed to load SSH host private key from {} (OpenSSH private key expected)",
            path.display()
        )
    })
}

fn load_authorized_keys(path: &Path) -> Result<Vec<ssh_key::PublicKey>> {
    let entries = AuthorizedKeys::read_file(path).with_context(|| {
        format!(
            "failed to read SSH authorized_keys file from {}",
            path.display()
        )
    })?;

    let mut keys = Vec::with_capacity(entries.len());
    for entry in entries {
        keys.push(entry.public_key().clone());
    }

    if keys.is_empty() {
        return Err(anyhow!(
            "SSH authorized_keys file {} does not contain any usable keys",
            path.display()
        ));
    }

    Ok(keys)
}
