use std::env;

use anyhow::Result;
use sshr::ssh::{
    AuthMethod, ExecRequest, ExecService, FileService, ReadFileRequest, RusshTool, SshConfig,
};

fn remote_test_config() -> Option<SshConfig> {
    dotenvy::dotenv().ok();

    let host = env::var("SSH_HOST").ok()?;
    let port = env::var("SSH_PORT")
        .ok()
        .and_then(|value| value.parse::<u16>().ok())
        .unwrap_or(22);
    let user = env::var("SSH_USER").ok()?;
    let timeout_ms = env::var("SSH_TIMEOUT_MS")
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .unwrap_or(8_000);

    let auth = if let Ok(key_path) = env::var("SSH_KEY_PATH") {
        let passphrase = env::var("SSH_KEY_PASSPHRASE").ok();
        AuthMethod::PrivateKey {
            private_key_path: key_path,
            passphrase,
        }
    } else if let Ok(password) = env::var("SSH_PASSWORD") {
        AuthMethod::Password { password }
    } else {
        return None;
    };

    Some(SshConfig {
        host,
        port,
        user,
        auth,
        timeout_ms,
        host_key_policy: Default::default(),
        known_hosts_path: None,
    })
}

fn skip_if_unconfigured() -> Option<SshConfig> {
    let cfg = remote_test_config();
    if cfg.is_none() {
        eprintln!("skip remote SSH tests: SSH_HOST/SSH_USER/SSH_PASSWORD is not configured");
    }
    cfg
}

async fn validated_config() -> Result<Option<SshConfig>> {
    let Some(cfg) = skip_if_unconfigured() else {
        return Ok(None);
    };

    let tool = RusshTool::new();
    match ExecService::validate(&tool, cfg.clone()).await {
        Ok(()) => Ok(Some(cfg)),
        Err(err) => {
            eprintln!("skip remote SSH tests: unable to connect to configured target: {err}");
            Ok(None)
        }
    }
}

#[tokio::test]
async fn exec_command_supports_read_only_probe() -> Result<()> {
    let Some(cfg) = validated_config().await? else {
        return Ok(());
    };

    let tool = RusshTool::new();
    let result = tool
        .exec(cfg, sshr::ssh::ExecRequest::new("uname -a"))
        .await?;

    assert!(!result.timed_out);
    assert_eq!(result.exit_code, 0);
    assert!(!result.stdout_string().trim().is_empty());
    Ok(())
}

#[tokio::test]
async fn exec_command_with_shell_supports_read_only() -> Result<()> {
    let Some(cfg) = validated_config().await? else {
        return Ok(());
    };

    let tool = RusshTool::new();
    let result = tool
        .exec(
            cfg,
            ExecRequest {
                command: "pwd".to_string(),
                use_shell: true,
                ..ExecRequest::default()
            },
        )
        .await?;

    assert!(!result.timed_out);
    assert_eq!(result.exit_code, 0);
    assert!(!result.stdout_string().trim().is_empty());
    Ok(())
}

#[tokio::test]
async fn detect_system_uses_read_only_commands() -> Result<()> {
    let Some(cfg) = validated_config().await? else {
        return Ok(());
    };

    let tool = RusshTool::new();
    let profile = tool.detect_system(cfg).await?;

    assert!(profile.arch.is_some());
    assert!(profile.os_id.is_some() || profile.package_manager.is_some());
    Ok(())
}

#[tokio::test]
async fn read_file_supports_read_only_file_access() -> Result<()> {
    let Some(cfg) = validated_config().await? else {
        return Ok(());
    };

    let tool = RusshTool::new();
    let content = tool
        .read_file(
            cfg,
            ReadFileRequest {
                path: "/etc/os-release".to_string(),
                offset: None,
                limit: Some(512),
            },
        )
        .await?;

    let text = String::from_utf8_lossy(&content);
    assert!(!text.trim().is_empty());
    assert!(text.contains("ID=") || text.contains("NAME="));
    Ok(())
}

#[tokio::test]
async fn list_directory_supports_read_only_listing() -> Result<()> {
    let Some(cfg) = validated_config().await? else {
        return Ok(());
    };

    let tool = RusshTool::new();
    let entries = tool.list_dir(cfg, "/etc".to_string()).await?;

    assert!(!entries.is_empty());
    assert!(entries.iter().all(|entry| !entry.name.is_empty()));
    Ok(())
}
