package Net::SSH::Any::Backend::Net_OpenSSH;

use strict;
use warnings;

BEGIN { die "Net::OpenSSH does not work on Windows" if $^O =~ /Win(?:32|64)/ }

use Carp;
our @CARP_NOT = qw(Net::SSH::Any);

use Net::SSH::Any::Util;
use Net::SSH::Any::Constants qw(:error);
use Net::OpenSSH;
use Net::OpenSSH::Constants qw(:error);

sub _backend_api_version { 1 }

sub _connect {
    my $any = shift;
    my %opts = map { $_ => $any->{$_} } qw(host port user password passphrase key_path timeout);
    $opts{default_stdin_discard} = 1;
    # $opts{default_stdout_discard} = 1;
    # $opts{default_stderr_discard} = 1;
    if (my $extra = $any->{backend_opts}{$any->{backend}}) {
        @opts{keys %$extra} = values %$extra;
    }
    $any->{be_ssh} = Net::OpenSSH->new(%opts);
    __check_error($any);
}

sub __make_proxy_method {
    my $name = shift;
    my $sub = sub {
        my ($any, $opts, $cmd) = @_;
        my $ssh = __ssh($any) or return undef;
        if (wantarray) {
            my @r = $ssh->$name($opts, $cmd);
            __check_error($any);
            return @r;
        }
        else {
            my $r = $ssh->$name($opts, $cmd);
            __check_error($any);
            return $r;
        }
    };
    no strict 'refs';
    *{"_$name"} = $sub;
}

__make_proxy_method 'capture';
__make_proxy_method 'capture2';
__make_proxy_method 'system';

my @error_translation;
$error_translation[OSSH_MASTER_FAILED    ] = SSHA_CONNECTION_ERROR;
$error_translation[OSSH_SLAVE_FAILED     ] = SSHA_CHANNEL_ERROR;
$error_translation[OSSH_SLAVE_PIPE_FAILED] = SSHA_LOCAL_IO_ERROR;
$error_translation[OSSH_SLAVE_TIMEOUT    ] = SSHA_TIMEOUT_ERROR;
$error_translation[OSSH_SLAVE_CMD_FAILED ] = SSHA_REMOTE_CMD_ERROR;
$error_translation[OSSH_SLAVE_SFTP_FAILED] = SSHA_CHANNEL_ERROR;
$error_translation[OSSH_ENCODING_ERROR   ] = SSHA_ENCODING_ERROR;

sub __check_error {
    my $any = shift;
    if (my $ssh = $any->{be_ssh}) {
        my $error = $ssh->error or return 1;
        $any->_set_error($error_translation[$error] // SSHA_CHANNEL_ERROR, $error);
    }
    else {
        $any->_set_error(SSHA_CONNECTION_ERROR, "Unable to create Net::OpenSSH object");
    }
    return;
}

sub __ssh {
    my $any = shift;
    my $ssh = $any->{be_ssh};
    $ssh and $ssh->wait_for_master and return $ssh;
    __check_error($any);
    undef;
}

sub _pipe {
    my ($any, $opts, $cmd) = @_;
    my $ssh = __ssh($any) or return undef;
    my ($socket, $pid) = $ssh->open2socket($opts, $cmd);
    __check_error($any) or return;
    require Net::SSH::Any::Backend::Net_OpenSSH::Pipe;
    Net::SSH::Any::Backend::Net_OpenSSH::Pipe->_upgrade_socket($socket, $pid, $any);
}

sub _sftp {
    my ($any, $opts) = @_;
    my $ssh = __ssh($any) or return undef;
    my $sftp = $ssh->sftp(%$opts);
    __check_error($ssh);
    return $sftp;
}

1;
