# frozen_string_literal: true

require 'terminal-table'
require 'paint'
require 'thor'
require 'launchy'

module Gush
  class CLI < Thor
    class_option :gushfile, desc: "configuration file to use", aliases: "-f"
    class_option :redis, desc: "Redis URL to use", aliases: "-r"
    class_option :namespace, desc: "namespace to run jobs in", aliases: "-n"

    def initialize(*)
      super
      Gush.configure do |config|
        config.gushfile           = options.fetch("gushfile",    config.gushfile)
        config.concurrency        = options.fetch("concurrency", config.concurrency)
        config.redis_url          = options.fetch("redis",       config.redis_url)
        config.namespace          = options.fetch("namespace",   config.namespace)
        config.ttl                = options.fetch("ttl",         config.ttl)
        config.locking_duration   = options.fetch("locking_duration", config.locking_duration)
        config.polling_interval   = options.fetch("polling_interval", config.polling_interval)
      end
      load_gushfile
    end

    desc "create WORKFLOW_CLASS", "Registers new workflow"
    def create(name)
      workflow = client.create_workflow(name)
      puts "Workflow created with id: #{workflow.id}"
      puts "Start it with command: gush start #{workflow.id}"
    end

    desc "start WORKFLOW_ID [ARG ...]", "Starts Workflow with given ID"
    def start(*args)
      id = args.shift
      workflow = client.find_workflow(id)
      client.start_workflow(workflow, args)
    end

    desc "create_and_start WORKFLOW_CLASS [ARG ...]", "Create and instantly start the new workflow"
    def create_and_start(name, *args)
      workflow = client.create_workflow(name)
      client.start_workflow(workflow.id, args)
      puts "Created and started workflow with id: #{workflow.id}"
    end

    desc "stop WORKFLOW_ID", "Stops Workflow with given ID"
    def stop(*args)
      id = args.shift
      client.stop_workflow(id)
    end

    desc "show WORKFLOW_ID", "Shows details about workflow with given ID"
    option :skip_overview, type: :boolean
    option :skip_jobs, type: :boolean
    option :jobs, default: :all
    def show(workflow_id)
      workflow = client.find_workflow(workflow_id)

      display_overview_for(workflow) unless options[:skip_overview]

      display_jobs_list_for(workflow, options[:jobs]) unless options[:skip_jobs]
    end

    desc "rm WORKFLOW_ID", "Delete workflow with given ID"
    def rm(workflow_id)
      workflow = client.find_workflow(workflow_id)
      client.destroy_workflow(workflow)
    end

    desc "list START STOP", "Lists workflows from START index through STOP index with their statuses"
    option :start, type: :numeric, default: nil
    option :stop, type: :numeric, default: nil
    def list(start=nil, stop=nil)
      workflows = client.workflow_ids(start, stop).map do |id|
        client.find_workflow(id)
      end

      rows = workflows.map do |workflow|
        [workflow.id, (Time.at(workflow.started_at) if workflow.started_at), workflow.class, {alignment: :center, value: status_for(workflow)}]
      end
      headers = [
        {alignment: :center, value: 'id'},
        {alignment: :center, value: 'started at'},
        {alignment: :center, value: 'name'},
        {alignment: :center, value: 'status'}
      ]
      puts Terminal::Table.new(headings: headers, rows: rows)
    end

    desc "viz {WORKFLOW_CLASS|WORKFLOW_ID}", "Displays graph, visualising job dependencies"
    option :filename, type: :string, default: nil
    option :open, type: :boolean, default: nil
    def viz(class_or_id)
      client

      begin
        workflow = client.find_workflow(class_or_id)
      rescue WorkflowNotFound
        workflow = nil
      end

      unless workflow
        begin
          workflow = class_or_id.constantize.new
        rescue NameError => e
          warn Paint["'#{class_or_id}' is not a valid workflow class or id", :red]
          exit 1
        end
      end

      opts = {}

      if options[:filename]
        opts[:filename], opts[:path] = File.split(options[:filename])
      end

      graph = Graph.new(workflow, **opts)
      graph.viz

      if (options[:open].nil? && !options[:filename]) || options[:open]
        Launchy.open Pathname.new(graph.path).realpath.to_s
      end
    end

    desc "migrate", "Runs all unapplied migrations to Gush storage"
    def migrate
      Dir[File.join(__dir__, 'migrate', '*.rb')].each {|file| require file }

      applied = Gush::Migration.subclasses.sort(&:version).count do |klass|
        migration = klass.new
        next if migration.migrated?

        puts "Migrating to #{klass.name} (#{migration.version})"
        migration.migrate
        puts "== #{migration.version} #{klass.name}: migrated ==="

        true
      end

      puts "#{applied} #{'migrations'.pluralize(applied)} applied"
    end

    private

    def client
      @client ||= Client.new
    end

    def overview(workflow)
      CLI::Overview.new(workflow)
    end

    def display_overview_for(workflow)
      puts overview(workflow).table
    end

    def status_for(workflow)
      overview(workflow).status
    end

    def display_jobs_list_for(workflow, jobs)
      puts overview(workflow).jobs_list(jobs)
    end

    def gushfile
      Gush.configuration.gushfile
    end

    def load_gushfile
      file = client.configuration.gushfile

      unless gushfile.exist?
        raise Thor::Error, Paint["#{file} not found, please add it to your project", :red]
      end

      load file.to_s
    rescue LoadError
      raise Thor::Error, Paint["failed to require #{file}", :red]
    end
  end
end
