require_relative 'default'

module Common

  # General

  def self.packages(ctx, *pkgs, action: :install)
    Array(pkgs).flatten.each do |pkg|
      ctx.package pkg do
        action action
      end
    end
  end

  def self.directories(ctx, dirs, opts = {})
    dirs = Array(dirs).compact.uniq
    owner     = opts[:owner]      || Default.user
    group     = opts[:group]      || Default.group
    mode      = opts[:mode]       || '0755'
    recursive = opts[:recursive]  || true
    ignore_failure = !!opts[:ignore_failure]
    recreate  = !!opts[:recreate] || false

    if recreate
      sort_dir(dirs).each { |dir| delete_dir(ctx, dir) }
    end
    dirs.each { |dir| create_dir(ctx, dir, owner, group, mode, recursive, ignore_failure) }
  end

  # System

  def self.daemon(ctx, name)
    Ctx.find(ctx, :execute, name) do
      command 'systemctl daemon-reload'
      action :nothing
    end
  end

  def self.application(ctx, name, user: Default.user, group: Default.group,
      exec: nil, cwd: nil, unit: {}, actions: [:enable, :start], subscribe: nil, reload: 'systemd_reload',
      restart: 'on-failure', restart_delay: 10, restart_limit: 10, restart_max: 600,
      verify: true, verify_timeout: 60, verify_interval: 5, verify_cmd: "systemctl is-active --quiet #{name}")

    if exec
      daemon(ctx, reload)

      defaults = {
        'Unit' => {
          'Description' => name.capitalize, 'After' => 'network.target',
          'StartLimitBurst' => restart_limit,
          'StartLimitIntervalSec' => restart_max
        },
        'Service' => ( {
          'Type' => 'simple', 'User' => user, 'Group' => group,
          'Restart' => restart,
          'RestartSec' => restart_delay
          }.merge(exec ? { 'ExecStart' => exec } : {})
           .merge(cwd ? { 'WorkingDirectory' => cwd } : {}) ),
        'Install' => { 'WantedBy' => 'multi-user.target' }
      }

      unit_config = defaults.dup
      unit.each { |section, settings| unit_config[section] = (unit_config[section] || {}).merge(settings) }
      unit_content = unit_config.map do |section, settings|
        lines = settings.map { |k, v| "#{k}=#{v}" unless v.nil? }.compact.join("\n")
        "[#{section}]\n#{lines}"
      end.join("\n\n")

      # Mask default application service
      conflicts = %Q(systemctl list-unit-files '*#{File.basename(exec.split.first)}*.service' --no-legend | awk '$2!="masked" {print $1}')
      Ctx.dsl(ctx).execute "application_mask_#{name}" do
        command "#{conflicts} | xargs -r -IUNIT sh -c 'systemctl stop UNIT && systemctl mask UNIT'"
        only_if conflicts
        returns [0, 123] # already masked returns '123'
        action  :run
      end

      Ctx.dsl(ctx).file "/etc/systemd/system/#{name}.service" do
        owner   'root'
        group   'root'
        mode    '0664'
        content unit_content
        notifies :run, "execute[application_reset_#{name}]", :immediately
        notifies :run, "execute[#{reload}]", :immediately
        notifies :restart, "service[#{name}]", :delayed
      end

    end

    Ctx.dsl(ctx).execute "application_reset_#{name}" do
      command "systemctl reset-failed #{name}.service"
      only_if "systemctl is-failed --quiet #{name}.service"
      action :nothing
    end

    if actions.include?(:force_restart)
      Ctx.dsl(ctx).execute "application_restart_#{name}" do
        command "systemctl stop #{name} || true && sleep 1 && systemctl start #{name}"
        action :run
      end
    else
      Ctx.dsl(ctx).service name do
        action actions
        Array(subscribe).flatten.each { |ref| subscribes :restart, ref, :delayed } if subscribe
      end
    end

    Ctx.dsl(ctx).ruby_block "application_verify_service_#{name}" do
      block do
        retry_timeout = Time.now + verify_timeout
        ok = false
        while Time.now < retry_timeout
          (is_active = Mixlib::ShellOut.new(verify_cmd)).run_command
          is_active.exitstatus.zero? ? (ok = true; break) : (sleep verify_interval)
        end
        raise (Logs.debug(["service '#{name}' failed health check",
          Mixlib::ShellOut.new("systemctl status #{name} --no-pager").run_command.stdout.strip,
          Mixlib::ShellOut.new("journalctl -u #{name} --no-pager").run_command.stdout.strip,
        ], level: :error)) unless ok
      end
      only_if { verify }
    end
  end

  def self.create_dir(ctx, dir, owner, group, mode, recursive, ignore_failure = false)
    Ctx.dsl(ctx).directory dir do owner owner; group group; mode mode; recursive recursive; ignore_failure ignore_failure; end
  rescue => e
    Logs.warn("Skip create #{dir}: #{e}")
  end

  def self.delete_dir(ctx, dir)
    Logs.try!("delete dir #{dir}") do
      Ctx.dsl(ctx).directory dir do action :delete; recursive true; only_if { ::Dir.exist?(dir) } end
    end
  end

  def self.sort_dir(dirs)
    Array(dirs).compact.sort_by { |d| -d.count('/') }
  end

end