require 'erb'
require 'yaml'
require 'sinatra/base'

require 'sidekiq'
require 'sidekiq/api'
require 'sidekiq/paginator'
require 'sidekiq/web_helpers'

module Sidekiq
  class Web < Sinatra::Base
    include Sidekiq::Paginator

    enable :sessions
    use Rack::Protection, :use => :authenticity_token unless ENV['RACK_ENV'] == 'test'

    set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
    set :public_folder, proc { "#{root}/assets" }
    set :views, proc { "#{root}/views" }
    set :locales, ["#{root}/locales"]

    helpers WebHelpers

    DEFAULT_TABS = {
      "Dashboard" => '',
      "Busy"      => 'busy',
      "Queues"    => 'queues',
      "Retries"   => 'retries',
      "Scheduled" => 'scheduled',
      "Dead"      => 'morgue',
    }

    class << self
      def default_tabs
        DEFAULT_TABS
      end

      def custom_tabs
        @custom_tabs ||= {}
      end
      alias_method :tabs, :custom_tabs

      attr_accessor :app_url
    end

    get "/busy" do
      erb :busy
    end

    post "/busy" do
      if params['identity']
        p = Sidekiq::Process.new('identity' => params['identity'])
        p.quiet! if params[:quiet]
        p.stop! if params[:stop]
      else
        processes.each do |pro|
          pro.quiet! if params[:quiet]
          pro.stop! if params[:stop]
        end
      end
      redirect "#{root_path}busy"
    end

    get "/queues" do
      @queues = Sidekiq::Queue.all
      erb :queues
    end

    get "/queues/:name" do
      halt 404 unless params[:name]
      @count = (params[:count] || 25).to_i
      @name = params[:name]
      @queue = Sidekiq::Queue.new(@name)
      (@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count)
      @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
      erb :queue
    end

    post "/queues/:name" do
      Sidekiq::Queue.new(params[:name]).clear
      redirect "#{root_path}queues"
    end

    post "/queues/:name/delete" do
      Sidekiq::Job.new(params[:key_val], params[:name]).delete
      redirect_with_query("#{root_path}queues/#{params[:name]}")
    end

    get '/morgue' do
      @count = (params[:count] || 25).to_i
      (@current_page, @total_size, @dead) = page("dead", params[:page], @count, reverse: true)
      @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
      erb :morgue
    end

    get "/morgue/:key" do
      halt 404 unless params['key']
      @dead = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first
      redirect "#{root_path}morgue" if @dead.nil?
      erb :dead
    end

    post '/morgue' do
      redirect request.path unless params['key']

      params['key'].each do |key|
        job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
        retry_or_delete_or_kill job, params if job
      end
      redirect_with_query("#{root_path}morgue")
    end

    post "/morgue/all/delete" do
      Sidekiq::DeadSet.new.clear
      redirect "#{root_path}morgue"
    end

    post "/morgue/all/retry" do
      Sidekiq::DeadSet.new.retry_all
      redirect "#{root_path}morgue"
    end

    post "/morgue/:key" do
      halt 404 unless params['key']
      job = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first
      retry_or_delete_or_kill job, params if job
      redirect_with_query("#{root_path}morgue")
    end


    get '/retries' do
      @count = (params[:count] || 25).to_i
      (@current_page, @total_size, @retries) = page("retry", params[:page], @count)
      @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
      erb :retries
    end

    get "/retries/:key" do
      @retry = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first
      redirect "#{root_path}retries" if @retry.nil?
      erb :retry
    end

    post '/retries' do
      redirect request.path unless params['key']

      params['key'].each do |key|
        job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
        retry_or_delete_or_kill job, params if job
      end
      redirect_with_query("#{root_path}retries")
    end

    post "/retries/all/delete" do
      Sidekiq::RetrySet.new.clear
      redirect "#{root_path}retries"
    end

    post "/retries/all/retry" do
      Sidekiq::RetrySet.new.retry_all
      redirect "#{root_path}retries"
    end

    post "/retries/:key" do
      job = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first
      retry_or_delete_or_kill job, params if job
      redirect_with_query("#{root_path}retries")
    end

    get '/scheduled' do
      @count = (params[:count] || 25).to_i
      (@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
      @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
      erb :scheduled
    end

    get "/scheduled/:key" do
      @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
      redirect "#{root_path}scheduled" if @job.nil?
      erb :scheduled_job_info
    end

    post '/scheduled' do
      redirect request.path unless params['key']

      params['key'].each do |key|
        job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
        delete_or_add_queue job, params if job
      end
      redirect_with_query("#{root_path}scheduled")
    end

    post "/scheduled/:key" do
      halt 404 unless params['key']
      job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
      delete_or_add_queue job, params if job
      redirect_with_query("#{root_path}scheduled")
    end

    get '/' do
      @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
      stats_history = Sidekiq::Stats::History.new((params[:days] || 30).to_i)
      @processed_history = stats_history.processed
      @failed_history = stats_history.failed
      erb :dashboard
    end

    REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)

    get '/dashboard/stats' do
      redirect "#{root_path}stats"
    end

    get '/stats' do
      sidekiq_stats = Sidekiq::Stats.new
      redis_stats   = redis_info.select { |k, v| REDIS_KEYS.include? k }

      content_type :json
      Sidekiq.dump_json(
        sidekiq: {
          processed:       sidekiq_stats.processed,
          failed:          sidekiq_stats.failed,
          busy:            sidekiq_stats.workers_size,
          processes:       sidekiq_stats.processes_size,
          enqueued:        sidekiq_stats.enqueued,
          scheduled:       sidekiq_stats.scheduled_size,
          retries:         sidekiq_stats.retry_size,
          dead:            sidekiq_stats.dead_size,
          default_latency: sidekiq_stats.default_queue_latency
        },
        redis: redis_stats
      )
    end

    get '/stats/queues' do
      queue_stats = Sidekiq::Stats::Queues.new

      content_type :json
      Sidekiq.dump_json(
        queue_stats.lengths
      )
    end

    private

    def retry_or_delete_or_kill job, params
      if params['retry']
        job.retry
      elsif params['delete']
        job.delete
      elsif params['kill']
        job.kill
      end
    end

    def delete_or_add_queue job, params
      if params['delete']
        job.delete
      elsif params['add_to_queue']
        job.add_to_queue
      end
    end
  end
end

if defined?(::ActionDispatch::Request::Session) &&
    !::ActionDispatch::Request::Session.respond_to?(:each)
  # mperham/sidekiq#2460
  # Rack apps can't reuse the Rails session store without
  # this monkeypatch
  class ActionDispatch::Request::Session
    def each(&block)
      hash = self.to_hash
      hash.each(&block)
    end
  end
end
