package Net::Connector::Cisco::Ios;

use 5.014;
use utf8;
use Moose;
use Carp;
use Data::Dumper;
use JSON;
use strict;
use Expect;

has host => (
  is       => 'ro',
  required => 1,
);

has username => (
  is       => 'ro',
  required => 0,
  default  => 'read'
);

has password => (
  is       => 'ro',
  required => 0,
  default  => '',
);

has enpassword => (
  is       => 'ro',
  required => 0,
);

has proto => (
  is       => 'ro',
  required => 0,
  default  => 'ssh',
);

has os => (
  is       => 'ro',
  required => 0,
  default  => 'IOS',
);

has _login_ => (
  is       => 'ro',
  required => 0,
  default  => 0,
);

has _enable_ => (
  is       => 'ro',
  required => 0,
  default  => 0,
);


sub login {
  my $self = shift;
  return if $self->{_login_};
  eval {
    $self->connect() if not defined $self->{exp};
  };
  if ($@) {
    if ($@ =~ /RSA modulus too small/) {
      eval {
        $self->connect(' -1 ');
      };
      if ($@) {
        return { success => 0, reason => $@ };
      }
    }
    elsif ($@ =~ /Selected cipher type <unknown> not supported by server/i) {
      eval {
        $self->connect('-c des ');
      };
      if ($@) {
        return { success => 0, reason => $@ };
      }
    }
    elsif ($@ =~ /Connection refused/i) {
      eval {
        $self->{proto} = 'telnet';
        $self->connect();
      };
      if ($@) {
        return { success => 0, reason => $@ };
      }
    }
    elsif ($@ =~ /IDENTIFICATION CHANGED/i) {
      system("/usr/bin/ssh-keygen -R $self->{host}");
      eval {
        $self->connect();
      };
      if ($@) {
        return { success => 0, reason => $@ };
      }
    }
    else {
      return { success => 0, reason => $@ };
    }
  }
  return { success => $self->{_login_} };
}

sub connect {
  my ($self, $args) = @_;
  $args = "" if not defined $args;
  my $username = $self->{username};
  my $password = $self->{password};
  my $host = $self->{host};
  my $prompt = '^\s*\S+[#>]\s*$';
  my $loginTime = 1;

  my $exp = Expect->new;
  $self->{exp} = $exp;
  $exp->raw_pty(1);
  $exp->restart_timeout_upon_receive(1);
  $exp->log_stdout(0);
  my $command = $self->{proto} . " $args" . " -l $username $host";
  $exp->spawn($command) or die "Cannot spawn $command: $!\n";

  my @ret = $exp->expect(10,
    [ qr/\(yes\/no\)\?\s*$/ => sub {
      $exp->send("yes\n");
      exp_continue
    } ],
    [ qr/assword:\s*$/ => sub {
      if ($loginTime) {
        $loginTime = 0;
        $exp->send("$password\n")
      }
      else {croak("username or password is wrong!")}; exp_continue
    } ],
    [ qr/ogin:\s*$/ => sub {
      $exp->send("$username\n");
      exp_continue
    } ],
    [ qr/name:\s*$/ => sub {
      $exp->send("$username\n");
      exp_continue
    } ],
    [ qr/REMOTE HOST IDEN/ => sub {croak("IDENTIFICATION CHANGED!");} ],
    [ qr/$prompt/m => sub {$self->{_login_} = 1;} ],
  );
  if (defined $ret[1]) {
    confess($ret[3] . $ret[1]);
  }
  if ($exp->match() =~ />/) {
    $self->enable();
  }
  elsif ($exp->match() =~ /#/) {
    $self->{_enable_} = 1;
  }
  return 1;
}

sub getconfig {
  my $self = shift;
  #if (not defined $self->{enable}){
  #    $self->enable();
  #}
  #if (! $self->{enable}){
  #    return {success=>0,reason=>"enable password not correct"};
  #}
  my $pageCommand = 'terminal length 0';
  $pageCommand = 'terminal page 0' if $self->{os} eq 'ASA';
  my @commands = ($pageCommand, "show run | exclude !Time");
  my $config = $self->execCommands(\@commands);
  my $lines = "";
  if ($config->{success} == 1) {
    $lines = $config->{result};
    $lines =~ s/^\s*ntp\s+clock-period\s+\d+\s*$//mi;
  }
  else {
    return $config;
  }
  return { success => 1, config => $lines };
}

sub send {
  my ($self, $command) = @_;
  my $exp = $self->{exp};
  $exp->send($command);
}

sub waitfor {
  my ($self, $prompt) = @_;
  $prompt = '^\s*\S+[>#]\s*\z' if not defined $prompt;
  my $exp = $self->{exp};
  my $buff = "";
  my @ret = $exp->expect(30,
    [ qr/^.+more\s*.+$/mi => sub {
      $exp->send(" ");
      $buff .= $exp->before();
      exp_continue
    } ],
    [ qr/\[startup-config\]\?/mi => sub {
      $exp->send(" ");
      $buff .= $exp->before() . $exp->match();
      exp_continue
    } ],
    [ qr/\[##+/ => sub {exp_continue} ],
    [ qr/$prompt/m => sub {$buff .= $exp->before() . $exp->match()} ]
  );
  $buff =~ s/\r\n|\n+\n/\n/g;
  $buff =~ s/\x{08}+\s+\x{08}+//g;
  $buff =~ s/^\s*$//gm;
  return $buff;
}

sub execCommands {
  my ($self, $commands) = @_;
  my $ret;
  if ($self->{_login_} == 0) {
    $ret = $self->login();
    return $ret if $ret->{success} == 0;
  }
  my $result = "";
  for my $cmd (@{$commands}) {
    $self->send($cmd . "\n");
    my $buff = $self->waitfor();
    if ($buff =~ /Invalid input detected\s+.+\^/i) {
      return { success => 0, failCommand => $cmd, reason => $result . $buff };
    }
    else {
      $result .= $buff;
    }
  }
  return { success => 1, result => $result };
}

sub enable {
  my $self = shift;
  my $enpass = $self->{enpassword} // $self->{password};
  my $username = $self->{username};
  my $exp = $self->{exp};
  my $enableTime = 1;
  $exp->send("enable\n");
  my @ret = $exp->expect(10,
    [ qr/assword:\s*$/ => sub {
      if ($enableTime) {
        $enableTime = 0;
        $exp->send("$enpass\n")
      }
      else {croak("enpassword is wrong!")}; exp_continue
    } ],
    [ qr/name:\s*$/i => sub {
      $exp->send("$username\n");
      exp_continue
    } ],
    [ qr/ogin:\s*$/i => sub {
      $exp->send("$username\n");
      exp_continue
    } ],
    [ qr/#\s*\z/ => sub {
      $self->{_enable_} = 1;
      return 1
    } ],
    [ qr/\^/i => sub {
      $self->{_enable_} = 0;
      croak("privileges is too low!")
    } ],
  );
  if (defined $ret[1]) {
    confess($ret[3] . $ret[1]);
  }
}


__PACKAGE__->meta->make_immutable;
1;