# frozen_string_literal: true

require "time"

module Tomo
  class Runtime
    autoload :ConcurrentRubyLoadError, "tomo/runtime/concurrent_ruby_load_error"
    autoload :ConcurrentRubyThreadPool, "tomo/runtime/concurrent_ruby_thread_pool"
    autoload :Context, "tomo/runtime/context"
    autoload :Current, "tomo/runtime/current"
    autoload :ExecutionPlan, "tomo/runtime/execution_plan"
    autoload :Explanation, "tomo/runtime/explanation"
    autoload :HostExecutionStep, "tomo/runtime/host_execution_step"
    autoload :InlineThreadPool, "tomo/runtime/inline_thread_pool"
    autoload :NoTasksError, "tomo/runtime/no_tasks_error"
    autoload :PrivilegedTask, "tomo/runtime/privileged_task"
    autoload :SettingsInterpolation, "tomo/runtime/settings_interpolation"
    autoload :SettingsRequiredError, "tomo/runtime/settings_required_error"
    autoload :TaskAbortedError, "tomo/runtime/task_aborted_error"
    autoload :TaskRunner, "tomo/runtime/task_runner"
    autoload :TemplateNotFoundError, "tomo/runtime/template_not_found_error"
    autoload :UnknownTaskError, "tomo/runtime/unknown_task_error"

    def self.local_user
      ENV["USER"] || ENV["USERNAME"] || `whoami`.chomp
    rescue StandardError
      nil
    end

    attr_reader :tasks

    def initialize(deploy_tasks:, setup_tasks:, hosts:, task_filter:, settings:, plugins_registry:)
      @deploy_tasks = deploy_tasks.freeze
      @setup_tasks = setup_tasks.freeze
      @hosts = hosts.freeze
      @task_filter = task_filter.freeze
      @settings = settings
      @plugins_registry = plugins_registry
      @tasks = plugins_registry.task_names
      freeze
    end

    def deploy!
      NoTasksError.raise_with(task_type: "deploy") if deploy_tasks.empty?

      execution_plan_for(deploy_tasks, release: :new).execute
    end

    def setup!
      NoTasksError.raise_with(task_type: "setup") if setup_tasks.empty?

      execution_plan_for(setup_tasks, release: :tmp).execute
    end

    def run!(task, *args, privileged: false)
      task = String.new(task).extend(PrivilegedTask) if privileged
      execution_plan_for([task], release: :current, args:).execute
    end

    def execution_plan_for(tasks, release: :current, args: [])
      ExecutionPlan.new(
        tasks:,
        hosts:,
        task_filter:,
        task_runner: new_task_runner(release, args)
      )
    end

    private

    attr_reader :deploy_tasks, :setup_tasks, :hosts, :task_filter, :settings, :plugins_registry

    def new_task_runner(release_type, args)
      run_settings = { release_path: release_path_for(release_type) }
        .merge(local_user: Runtime.local_user)
        .merge(settings)
        .merge(run_args: args)

      TaskRunner.new(plugins_registry:, settings: run_settings)
    end

    def release_path_for(type)
      start_time = Time.now
      release = start_time.utc.strftime("%Y%m%d%H%M%S")

      case type
      when :current then "%{current_path}"
      when :new then "%{releases_path}/#{release}"
      when :tmp then "%{tmp_path}/#{release}"
      else
        raise ArgumentError, "release: must be :current, :new, or :tmp"
      end
    end
  end
end
