require 'net/ssh'
require 'pathname'
require 'json'
require 'base64'
namespace :exec_commands do
  desc "Exec commands Script"
  task :exec_commands,[:base64_args,:site_construct_id,:commands,:type,:server_name,:rails_env,:commands_i18n] => :environment do |task,args|
    if args.base64_args.present?
      tmp = JSON.parse(Base64.decode64(args.base64_args)) rescue {}
      args = Rake::TaskArguments.new(tmp.keys, tmp.values)
    end
    @type = args.type
    if args.site_construct_id.present?
      @site_construct = SiteConstruct.find(args.site_construct_id)
      site_server = SiteServer.where(:server_name=>@site_construct.server_type).first
      site_servers = [site_server]
      @site_construct.update(:status=>"execing",:infos=>[""])
    else
      @site_construct = nil
      site_servers = SiteServer.where(:server_name.in=>args.server_name.split("////")).to_a
    end
    if args.type == "exec_all"
      Multithread.where(:key=>'execing_commands').each do |thread|
        if thread.status["status"] == "error" || thread.status["status"] == "finish"
          thread.destroy
        elsif thread.updated_at < (Time.now - 5.minute)
          thread.destroy
        end
      end
      @thread = Multithread.where(:key=>'execing_commands').first
    else
      @thread = nil
    end
    if @thread.nil?
      @command_trans = {}
      @command_relations = {"upgrade_site"=>'git fetch origin && rails runner "a=Admin::SitesController.new;a.git_reset(%w(origin)[0],%w(update)[0]);while(1) do (Multithread.where(key: %w(update_manager)[0],status: %w(finish)[0]).count!=0 ? break : nil) end;sleep(5)"',
            "bundle_update"=>"bundle update && kill -s USR2 `cat tmp/pids/unicorn.pid`",
            "restart_site"=>"kill -s TERM `fuser tmp/pids/unicorn.sock tmp/sockets/unicorn.sock tmp/unicorn.sock`;bundle exec unicorn_rails -c config/unicorn.rb -D -E {{rails_env}}",
            "get_template"=>"mongo --eval 'db.sites.find().pretty()[0][\"template\"]' {{db_name}} --quiet"}
      @preserve_output = ["get_template"]
      @command_relations.each do |k,v|
        @command_trans[k] = I18n.t("client_management.#{k}")
      end
      rails_envs = ["production", "development"]
      rails_envs.each do |env|
        @command_relations["start_site_in_env,#{env}"] = "kill -s TERM `fuser tmp/pids/unicorn.sock tmp/sockets/unicorn.sock tmp/unicorn.sock`;bundle exec unicorn_rails -c config/unicorn.rb -D -E #{env}"
        @command_trans["start_site_in_env,#{env}"] = I18n.t("client_management.start_site_in_env",{:env=>env})
      end
      begin
        if args.type == "exec_all"
          if @thread.nil? 
            @thread = Multithread.create(:key=>'execing_commands',:status=>{"infos"=>[],"status"=>"execing"})
          else
            @thread.update(:status=>{"infos"=>[],"status"=>"execing"})
          end
        end
        site_servers.each do |site_server|
          ip = site_server.ip
          server_port = site_server.port
          user = site_server.account
          password = site_server.password
          @password = password
          begin 
            Net::SSH.start(ip , user , { password: password, port: server_port}) do |ssh|
            end
          rescue Net::SSH::HostKeyMismatch
            system("ssh-keygen -f \"$HOME/.ssh/known_hosts\" -R #{ip}")
          rescue Errno::ENOTTY
            system("ssh-add \"$HOME/.ssh/id_rsa\"")
          end
          Net::SSH.start(ip , user , { password: password, port: server_port}) do |ssh|
            @site_construct.update!(:infos=>[]) rescue nil
            if args.type == 'close_site'
              exec_ssh_command_by_sudo_and_see_output(ssh,"chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R",false)
              exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'kill -s TERM `fuser #{@site_construct.path}/#{@site_construct.site_name}/tmp/unicorn.sock`'",false)
              update_infos_for_exec("finish closing #{@site_construct.site_name}")
              @site_construct.update(:status =>"closed")
            elsif args.type == 'open_site'
              default_rails_env = 'development'
              enable_rails_env = ['development','production']
              rails_env = @site_construct.rails_env.blank? ? default_rails_env : @site_construct.rails_env
              cmd_output = exec_ssh_command_by_sudo_and_see_output(ssh,"ps -o args= -p `cat #{@site_construct.path}/#{@site_construct.site_name}/tmp/pids/unicorn.pid`",false) rescue []
              enable_rails_env.each do |env|
                if( cmd_output[0].include?(env) rescue false)
                  rails_env = env
                  break
                elsif( cmd_output[0].include?("No such file or directory") rescue false)
                  @site_construct.update(:status => "closed")
                  break
                end
              end
              if args.rails_env.present?
                rails_env = args.rails_env
              end
              @site_construct.update(:rails_env=>rails_env)
              update_infos_for_exec("starting site to #{rails_env}")
              outputs = exec_ssh_command_by_sudo_and_see_output(ssh,"sudo -p 'sudo password:' chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R",false)
              output = exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}\nkill -s TERM `fuser tmp/unicorn.sock`\nsudo -p \"sudo password:\" kill -s TERM `sudo -p \"sudo password:\" fuser tmp/unicorn.sock`\nsudo -p \"sudo password:\" rm -f tmp/pids/unicorn.pid\nbundle exec unicorn_rails -c config/unicorn.rb -D -E #{rails_env}\n'",false)
              if output.include? "bundle install"
                update_infos_for_exec("not yet bundle install")
                outputs = exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name};bundle install'")
                if outputs.join("\n").include? "Username for"
                  update_infos_for_exec("Cannot finish bundle install.")
                  update_infos_for_exec("This site include an private git project.")
                  @site_construct.update(:status =>"error")
                  break
                else
                  exec_ssh_command_by_sudo_and_see_output(ssh,"bundle exec unicorn_rails -c config/unicorn.rb -D -E #{rails_env}",false)
                end
              end
              update_infos_for_exec("finish starting #{@site_construct.site_name}")
              @site_construct.update(:status =>"finish")
            else
              if args.type == 'exec_all'
                sites = SiteConstruct.where(:server_type=>site_server.server_name).to_a
              else
                sites = [@site_construct]
              end
              commands = args.commands
              if commands.class == String
                commands = commands.split("////").select{|c| c != ""}
              elsif commands.nil?
                commands = []
              end
              if commands.include?("{{create_users}}")
                commands = ['sudo -p "sudo password:" chmod 777 {{full_site_path}} -R',
                  'sudo -p "sudo password:" chmod 777 {{full_site_path}}/public -R',
                  'sudo -p "sudo password:" chmod 777 {{full_site_path}}/app/templates -R',
                  'sudo -p "sudo password:" useradd --home-dir {{full_site_path}}/app/templates {{site_name}} --no-create-home --badnames',
                  'sudo -p "sudo password:" bash -l -c \'echo -e "{{site_name}}\n{{site_name}}"|passwd {{site_name}}\'']
              end
              @commands_i18n = args.commands_i18n # command 客製化翻譯
              sites.each do |site_construct|
                @site_construct = site_construct
                @site_construct.update!(:infos=>[])
                exec_ssh_command_by_sudo(ssh,"chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R")
                rails_env = @site_construct.rails_env.blank? ? "development" : @site_construct.rails_env
                commands.each_with_index do |command, idx|
                  @command_i18n = command
                  update_flag = 1
                  @command_relations.each do |k,v|
                    if command.include?("{{#{k}}}")
                      if @preserve_output.include?(k)
                        update_flag = 1
                      else
                        update_flag = 2 #Not logging commands result
                      end
                    end
                    command = command.gsub("{{#{k}}}",v)
                    @command_i18n = @command_i18n.gsub("{{#{k}}}",@command_trans[k])
                  end
                  if @commands_i18n
                    tmp = @commands_i18n[idx]
                    @command_i18n = tmp if tmp
                  end
                  command = command.gsub("{{rails_env}}",rails_env)
                  command = command.gsub("{{full_site_path}}",@site_construct.full_site_path)
                  command = command.gsub("{{site_name}}",@site_construct.site_name)
                  command = command.gsub("{{db_name}}",@site_construct.db_name)
                  @command_i18n = @command_i18n.gsub("{{rails_env}}",rails_env)
                  @command_i18n = @command_i18n.gsub("{{full_site_path}}",@site_construct.full_site_path)
                  @command_i18n = @command_i18n.gsub("{{site_name}}",@site_construct.site_name)
                  command = command.gsub("'","'\"'\"'")
                  exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name};#{command}'", update_flag)
                  @command_i18n = nil
                end
              end
            end
          end
        end
        if !@thread.nil?
          @thread.update(:status=>@thread.status.merge({"status"=>"finish"}))
        elsif @site_construct
          @site_construct.update(:status =>"finish")
        end
      rescue => e
        if !@thread.nil?
          @thread.update(:status=>{"infos"=>@thread.status["infos"].push(e.message),"status"=>"error"})
          @thread.update(:status=>{"infos"=>@thread.status["infos"].push(e.backtrace.join("\n")),"status"=>"error"})
        end
        if !@site_construct.nil?
          @site_construct.update(:status =>"error",:infos=>@site_construct.infos.push("#{e.message}"))
          @site_construct.update(:status =>"error",:infos=>@site_construct.infos.push("#{e.backtrace.join("\n")}"))
        end
      end
    end
  end
  def exec_ssh_command_by_sudo(session,command)
    output = session.exec!("echo '#{@password}' | sudo -S #{command}")
    # output = session.exec!("echo '#{@password}' | sudo -S -s #{command}")
    if output.include?("sudo:") && output.include?("command not found")
      output = session.exec!(command)
    end
    return output.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
  end
  def update_infos_for_exec(info,update_last=false,update_array=false)
    return if @site_construct.nil?
    if update_last && !@site_construct.infos.empty?
      @site_construct.infos[-1] += info.to_s
    else
      if update_array
        @site_construct.infos += info
      else
        @site_construct.infos.push(info.to_s)
      end
    end
    @site_construct.save!
    return @site_construct.infos
  end
  def update_thread_infos_for_exec(info,update_last=false,update_array=false)
    if update_last && !@thread.status["infos"].empty?
      @thread.status["infos"][-1] += info.to_s
    else
      if update_array
        @thread.status["infos"] += info
      else
        @thread.status["infos"].push(info.to_s)
      end
    end
    @thread.save!
    return @thread.status["infos"]
  end
  def exec_ssh_command_and_see_output(session,command,update_flag=1,output_string=false)
    outputs = []
    @flag = (@type == "exec_all")
    command_i18n = command
    update = (update_flag == true || update_flag == 1 || update_flag == 2)
    update_outputs = (update_flag == true || update_flag == 1)
    if update
      command_i18n = @command_i18n if @command_i18n
      if @site_construct
        update_thread_infos_for_exec(["execing #{command_i18n} on  on <a href='#{((@site_construct.get_port == "443") ? "https" : "http")}://#{@site_construct.domain_name}#{((@site_construct.get_port=="80" || @site_construct.get_port=="443" || @site_construct.get_port.blank?) ? "" : (':'+@site_construct.get_port))}'>#{@site_construct.domain_name}</a>",""],false,true) if @flag
        update_infos_for_exec(["execing #{command_i18n}",""],false,true)
      elsif @thread
        update_thread_infos_for_exec(["execing #{command_i18n}...",""],false,true)
      end
    end
    session.open_channel do |channel|
      channel.request_pty do |channel, success|
        channel.exec("LANG=en.UTF8 #{command}") do |ch, success|
          abort "could not execute command: #{command}" unless success
          channel.on_data do |ch, data|
            data_str = data.to_s
            data_str.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
            if data_str.include?("sudo password:") || data_str.include?("Password:")
              channel.send_data "#{@password}\n"
            else
              print data_str unless @no_stdout
              data_str.gsub!("\r\n", "\n")
              data_str.sub!(/\r$/, '')
              next if data_str.length == 0
              if data_str.include?("\n") || outputs.empty?
                output_lines = data_str.split("\n")
                first_output = output_lines[0]
                if first_output
                  if outputs.count != 0
                    outputs[-1] += first_output
                  else
                    outputs << first_output
                  end
                end
                new_arr = output_lines[1..-1]
                new_arr = [] if new_arr.nil?
                if data_str[-1] == "\n"
                  new_arr << ""
                end
                outputs += new_arr
                if update_outputs
                  if first_output
                    update_thread_infos_for_exec(first_output,true) if @flag
                    update_infos_for_exec(first_output,true)
                  end
                  update_thread_infos_for_exec(new_arr,false,true) if @flag
                  update_infos_for_exec(new_arr,false,true)
                end
              else
                if outputs.count == 0
                  outputs.push(data_str)
                else
                  outputs[-1] += (data_str rescue "")
                end
                if update_outputs
                  update_thread_infos_for_exec(data_str,true) if @flag
                  update_infos_for_exec(data_str,true)
                end
              end
            end
          end
          channel.on_close do |ch|
            if update
              if @site_construct
                update_thread_infos_for_exec("finish execing #{command_i18n} on <a href='#{((@site_construct.get_port == "443") ? "https" : "http")}://#{@site_construct.domain_name}#{((@site_construct.get_port=="80" || @site_construct.get_port=="443" || @site_construct.get_port.blank?) ? "" : (':'+@site_construct.get_port))}'>#{@site_construct.domain_name}</a>") if @flag
                update_infos_for_exec("finish execing #{command_i18n}")
              elsif @thread
                update_thread_infos_for_exec("finish execing #{command_i18n}")
              end
            end
          end
        end
      end
    end
    session.loop
    if output_string
      return outputs.join("\n")
    else
      return outputs
    end
  end
  def exec_ssh_command_by_sudo_and_see_output(session,command,update=1,output_string=false)
    outputs = exec_ssh_command_and_see_output(session,command,update)
    tmp = outputs.join("\n")
    if tmp.include?("Permission denied") || tmp.include?("Operation not permitted")
      outputs = exec_ssh_command_and_see_output(session,"sudo -p 'sudo password:' #{command}",update)
      tmp = outputs.join("\n")
    end
    if output_string
      return tmp
    else
      return outputs
    end
  end
end