use std::process::Command;

#[tauri::command]
pub async fn remove_known_host(
    ip: String,
) -> Result<String, String> {
    if ip.trim().is_empty() {
        return Err(
            "ip address cannot be empty".into(),
        );
    }

    let output = Command::new("ssh-keygen")
        .args(["-R", &ip])
        .output()
        .map_err(|error| {
            format!(
                "failed to run ssh-keygen: {}",
                error,
            )
        })?;

    if output.status.success() {
        Ok(format!(
            "removed stale known_hosts entry for {}",
            ip,
        ))
    } else {
        Err(String::from_utf8_lossy(&output.stderr)
            .to_string())
    }
}

#[tauri::command]
pub async fn probe_router_ssh(
    ip: String,
) -> Result<String, String> {
    if ip.trim().is_empty() {
        return Err(
            "router IP is required".into(),
        );
    }

    let output = Command::new("ssh")
        .args([
            "-o",
            "BatchMode=yes",
            "-o",
            "ConnectTimeout=5",
            "-o",
            "StrictHostKeyChecking=accept-new",

            // Legacy OpenWrt/Dropbear routers that only offer ssh-rsa.
            "-o",
            "HostKeyAlgorithms=+ssh-rsa",
            "-o",
            "PubkeyAcceptedAlgorithms=+ssh-rsa",

            &format!("root@{}", ip),
            "ubus call system board",
        ])
        .output()
        .map_err(|error| {
            format!(
                "failed to run ssh command: {}",
                error,
            )
        })?;

    let stdout =
        String::from_utf8_lossy(&output.stdout)
            .to_string();

    let stderr =
        String::from_utf8_lossy(&output.stderr)
            .to_string();

    if output.status.success() {
        return Ok(stdout);
    }

    if stderr.contains(
        "REMOTE HOST IDENTIFICATION HAS CHANGED",
    ) {
        return Err(format!(
            "STALE_HOST_KEY: SSH host key mismatch for {}. Remove known_hosts entry and retry.",
            ip,
        ));
    }

    if stderr.contains("Permission denied")
        || stderr.contains("publickey")
        || stderr.contains("password")
    {
        return Err(format!(
            "AUTH_REQUIRED: Router is reachable at {}, but SSH requires password login. Try manually: ssh root@{} then run: ubus call system board",
            ip, ip,
        ));
    }

    Err(format!(
        "SSH_FAILED: {}\n{}",
        stderr, stdout,
    ))
}

fn system_ssh_probe(ip: &str, command: &str) -> Result<String, String> {
    let output = Command::new("ssh")
        .args([
            "-o",
            "BatchMode=yes",
            "-o",
            "ConnectTimeout=5",
            "-o",
            "StrictHostKeyChecking=accept-new",
            "-o",
            "HostKeyAlgorithms=+ssh-rsa",
            "-o",
            "PubkeyAcceptedAlgorithms=+ssh-rsa",
            &format!("root@{}", ip),
            command,
        ])
        .output()
        .map_err(|error| format!("failed to run ssh command: {}", error))?;

    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();

    if output.status.success() {
        Ok(stdout)
    } else {
        Err(format!("SSH_FAILED: {}\n{}", stderr, stdout))
    }
}

fn is_ssh_auth_failure(error: &str) -> bool {
    let lowered = error.to_lowercase();

    lowered.contains("permission denied")
        || lowered.contains("publickey,password")
        || lowered.contains("authentication failed")
}


use ssh2::Session;
use std::{
    io::Read,
    net::{TcpStream, ToSocketAddrs},
    time::Duration,
};

#[tauri::command]
pub async fn inspect_router_with_password(
    ip: String,
    password: String,
) -> Result<String, String> {
    if ip.trim().is_empty() {
        return Err("router IP is required".into());
    }

    if password.trim().is_empty() {
        return Err("router password is required".into());
    }

    let address = format!("{}:22", ip);

    let socket_address = address
        .to_socket_addrs()
        .map_err(|error| {
            format!("failed to resolve router address: {}", error)
        })?
        .next()
        .ok_or_else(|| {
            format!("failed to resolve router address {}", address)
        })?;

    let tcp = TcpStream::connect_timeout(
        &socket_address,
        Duration::from_secs(5),
    )
    .map_err(|error| {
        format!("failed to connect to SSH on {}: {}", ip, error)
    })?;

    tcp.set_read_timeout(Some(Duration::from_secs(10)))
        .map_err(|error| {
            format!("failed to set SSH read timeout: {}", error)
        })?;

    tcp.set_write_timeout(Some(Duration::from_secs(10)))
        .map_err(|error| {
            format!("failed to set SSH write timeout: {}", error)
        })?;

    let mut session = Session::new()
        .map_err(|error| {
            format!("failed to create SSH session: {}", error)
        })?;

    session.set_tcp_stream(tcp);

    session.handshake().map_err(|error| {
        format!("SSH handshake failed for {}: {}", ip, error)
    })?;

    let auth_result = session.userauth_password("root", &password);

    if auth_result.is_err() || !session.authenticated() {
        let error = auth_result
            .err()
            .map(|error| error.to_string())
            .unwrap_or_else(|| format!("SSH authentication failed for root@{}", ip));

        // If password auth fails but the local OpenSSH agent has a working key,
        // still allow detection to succeed. This handles Windows/libssh2 agent gaps.
        return system_ssh_probe(&ip, "ubus call system board").map_err(|system_error| {
            if is_ssh_auth_failure(&system_error) {
                format!(
                    "SSH authentication failed for root@{}: {}; system ssh fallback also failed: {}",
                    ip, error, system_error
                )
            } else {
                system_error
            }
        });
    }

    let mut channel = session.channel_session().map_err(|error| {
        format!("failed to open SSH channel: {}", error)
    })?;

    channel
        .exec("ubus call system board")
        .map_err(|error| {
            format!("failed to run router inspection command: {}", error)
        })?;

    let mut stdout = String::new();

    channel
        .read_to_string(&mut stdout)
        .map_err(|error| {
            format!("failed to read router inspection output: {}", error)
        })?;

    channel.wait_close().map_err(|error| {
        format!("failed to close SSH channel: {}", error)
    })?;

    let exit_status = channel.exit_status().map_err(|error| {
        format!("failed to read SSH command exit status: {}", error)
    })?;

    if exit_status != 0 {
        return Err(format!(
            "router inspection command failed with exit code {}",
            exit_status
        ));
    }

    Ok(stdout)
}