require 'net/ssh'
require 'net/scp'

module Devops
  module SSH
    class Utils

      class << self

        # returns true if ssh available
        def try_ssh host, remote_user, ssh_key_path
          i = 1
          begin
            res = ""
            Net::SSH.start( host, remote_user, :keys => [ssh_key_path], :timeout => 2, :keys_only=>true, :non_interactive => true ) do |ssh|
              res = ssh.exec!('echo 1')
            end
            return res.strip == "1"
          rescue Net::SSH::ConnectionTimeout, Errno::ECONNREFUSED
            return false
          rescue Net::SSH::AuthenticationFailed => e
            if i < 3
              i = i + 1
              sleep(5)
              retry
            end
            raise e
          end
        end

        def run_command_out cmd, ip, remote_user, ssh_key_path, out
          if remote_user != "root"
            cmd = "sudo #{cmd}"
          end
          msg = "SSH: trying to run command '#{cmd}' on '#{ip}'"
          DevopsLogger.logger.info(msg)
          out.flush if out.respond_to?(:flush)
          exit_code = nil
          Net::SSH.start(ip, remote_user, :keys => [ssh_key_path], :keys_only=>true, :non_interactive => true) do |session|
            session.open_channel do |channel|
              channel.request_pty(:modes => { Net::SSH::Connection::Term::ECHO => 0 }) do |c, success|
                raise "could not request pty" unless success
                channel.exec cmd
                channel.on_data do |ch, data|
                  out << data + "\n"
                  out.flush if out.respond_to?(:flush)
                end
                channel.on_request("exit-status") do |ch, data|
                  exit_code = data.read_long
                end
              end
            end
            session.loop
          end
          msg = "SSH: done with code '#{exit_code}'"
          DevopsLogger.logger.info(msg)
          exit_code
        end

        def run_script script_path, ip, remote_user, ssh_key_path, out
          msg = "SSH: trying to run script '#{script_path}' on '#{ip}'"
          DevopsLogger.logger.info(msg)
          out << msg + "\n"
          out.flush if out.respond_to?(:flush)
          exit_code = nil
          remote_path = "/tmp/script-#{Time.now.to_i}"
          Net::SSH.start(ip, remote_user, :keys => [ssh_key_path], :keys_only=>true, :non_interactive => true) do |session|
            session.scp.upload script_path, remote_path
            session.open_channel do |channel|
              channel.request_pty(:modes => { Net::SSH::Connection::Term::ECHO => 0 }) do |c, success|
                raise "could not request pty" unless success
                channel.exec "/bin/bash #{remote_path} && rm -f #{remote_path}"
                channel.on_data do |ch, data|
                  out << data + "\n"
                  out.flush if out.respond_to?(:flush)
                end
                channel.on_request("exit-status") do |ch, data|
                  exit_code = data.read_long
                end
              end
            end
            session.loop
          end
          msg = "SSH: done with code '#{exit_code}'"
          DevopsLogger.logger.info(msg)
          out << msg + "\n"
          out.flush if out.respond_to?(:flush)
          exit_code
        end

        def upload_file data, ip, remote_user, ssh_key_path, remote_path
          DevopsLogger.logger.info("SCP: trying to upload data to '#{remote_user}@#{ip}:#{remote_path}' with identity file #{ssh_key_path}")
          Net::SSH.start(ip, remote_user, keys: [ssh_key_path], :keys_only => true, :non_interactive => true ) do |ssh|
            res = ssh.scp.upload StringIO.new(data.to_s), remote_path
            puts "UPLOAD RES: #{res}"
          end
          DevopsLogger.logger.info("SCP: done")
        end

        def copy_deploy_info data, ip, remote_user, ssh_key_path, out
          upload_file(data, ip, remote_user, ssh_key_path, "/tmp/deploy_info.json")
          cmd = "mv /tmp/deploy_info.json /etc/chef/"
          cmd = "sudo " + cmd unless remote_user == "root"
          run_command_out(cmd, ip, remote_user, ssh_key_path, out)
          "/etc/chef/deploy_info.json"
        end
      end

    end
  end
end
