require_relative 'rake_helpers'

require 'yaml' require 'awesome_print'

namespace :operator do

task :apply do

  set :operation_name, ENV['operation']

  raise_error_with_usage('Please set operation.') if fetch(:operation_name).nil?
  on roles(:all), in: :sequence do |host|
    puts "target: #{host}"
    tasks = load_tasks(fetch(:operation_name), nil)
    exit 1 if @undefined_var_flag

    @TASK_SIZE = tasks.size.freeze
    @TASK_INDEX = -1
    execute_tasks(tasks, host)
  end
end

def execute_tasks(tasks, host)
  tasks.each do |task|
    execute_commands_task(task, host) if task['execute_commands'] || task['check_commands']
  end
end

def confirm_task(task)
  @TASK_INDEX += 1
  while true
    debug "task: #{task.inspect}"
    debug 'ask [y/n/s/r]'
    puts "\n以下のタスクを実行しますか?"

    puts '======================================='
    puts_progress
    ap task
    puts '======================================='

    set :task_confirm, ask('any command (y:実行 n:中断 s:スキップ r:再確認) [y/n/s/r]')

    if fetch(:task_confirm) == 'y'
      puts "y: 実行します\n"
      debug 'y is selected'
      return true
    elsif fetch(:task_confirm) == 'n'
      puts "n: 中断します\n"
      debug 'n is selected'
      exit 1
    elsif fetch(:task_confirm) == 's'
      puts "s: スキップします\n"
      debug 's is selected'
      return false
    end
  end
end

def execute_commands_task(task, host)
  return unless confirm_task(task)

  if task['execute_commands']
    execute_all_commands(task['execute_commands'], host)
  end
  if task['check_commands']
    execute_each_commands_with_check(task)
  end
end

def execute_all_commands(task, host)
  task.each do |cmd|
    puts <<-EOS

execute: '#{cmd}'

    EOS
    send_command(cmd, host)
  end
end

def execute_each_commands_with_check(task)
  task['check_commands'].each do |cmd|
    while true
      output = send_check_command(cmd)
      debug 'ask [y/n/r]'

      enable_highlight(task['highlight']) if task['highlight']
      puts <<-EOS

check: '#{cmd}' result: #{output}

      EOS
      disable_highlight if task['highlight']

      set :answer, ask('any command (y:続行 n:中断 r:再確認) [y/n/r]')
      if fetch(:answer) == 'y'
        puts 'y: 続行します'
        debug 'y is selected'
        break
      elsif fetch(:answer) == 'n'
        puts 'n: 中断します'
        debug 'n is selected'
        exit 1
      end
    end
  end
end

def send_command(cmd, host)
  debug "execute: #{cmd}"
  puts "host: #{host.hostname}"
  if dry_run?
    try_to_execute(cmd)
  else
    run_interactively host do
      try_to_execute(cmd)
    end
  end
end

def send_check_command(cmd)
  debug "check: #{cmd}"
  result = try_to_capture(cmd)
  debug "check_result: #{result}"
  result
end

def load_operation_yml(file_name)
  file_path = File.join('.', 'operations', file_name)
  yml_extension = %w(.yml .yaml)
  if yml_extension.include?(File.extname(file_path))
    files = [file_path]
  else
    files = yml_extension.map {|ext| "#{file_path}#{ext}"}
  end
  files.each { |file| return YAML.load_file(file).merge(file: file) if File.exist?(file) }
  raise_error_with_usage("can not find the operation file: #{files.join(' or ')}")
end

def puts_progress
  puts "(#{@TASK_INDEX + 1}/#{@TASK_SIZE})"
end

def load_tasks(operation_name, imported_vars_hash)
  yml = load_operation_yml(operation_name)
  tasks = []

  yml['vars'].merge!(imported_vars_hash) if imported_vars_hash
  tmp_tasks = yml['vars'] ? apply_vars_to_tasks(yml['tasks'], yml['vars'], yml[:file]) : yml['tasks']
  tmp_tasks.each do |task|
    if task['import']
      import_tasks = load_tasks(task['import'], task['vars'])
      tasks.concat import_tasks
    else
      tasks.push task
    end
  end

  tasks
end

def apply_vars_to_tasks(tasks, vars, file)
  tasks.each_with_index do |task, i|
    task.each do |cmd_types, cmds|
      tasks[i][cmd_types] = apply_vars_to_task(cmds, vars, file)
    end
  end
  tasks
end

def apply_vars_to_task(cmds, vars, file)
  if cmds.kind_of?(String)
    cmds = apply_vars_to_cmd(cmds, vars, file)
  end
  if cmds.kind_of?(Array)
    cmds.each_with_index do |cmd, i|
      cmds[i] = apply_vars_to_cmd(cmd, vars, file)
    end
  end
  if cmds.kind_of?(Hash)
    cmds.each do |import_var_name, import_var_val|
      cmds[import_var_name] = apply_vars_to_cmd(import_var_val, vars, file)
    end
  end
  cmds
end

def apply_vars_to_cmd(cmd, vars, file)
  undefined_var = []
  var_pattern = /{{([-0-9a-zA-Z_]+)}}/
  cmd.gsub!(var_pattern) do |var|
    undefined_var << var unless vars[var[var_pattern, 1]]
    vars[var[var_pattern, 1]]
  end
  if undefined_var.count > 0
    @undefined_var_flag = true
    warn_highlight { puts("#{file}: variable is undefined: #{undefined_var.join(', ')}") }
  end
  cmd
end

end