# frozen_string_literal: true

module Tomo
  class Configuration
    autoload :DSL, "tomo/configuration/dsl"
    autoload :Environment, "tomo/configuration/environment"
    autoload :Glob, "tomo/configuration/glob"
    autoload :PluginFileNotFoundError, "tomo/configuration/plugin_file_not_found_error"
    autoload :PluginsRegistry, "tomo/configuration/plugins_registry"
    autoload :ProjectNotFoundError, "tomo/configuration/project_not_found_error"
    autoload :RoleBasedTaskFilter, "tomo/configuration/role_based_task_filter"
    autoload :UnknownEnvironmentError, "tomo/configuration/unknown_environment_error"
    autoload :UnknownPluginError, "tomo/configuration/unknown_plugin_error"
    autoload :UnspecifiedEnvironmentError, "tomo/configuration/unspecified_environment_error"

    def self.from_config_rb(path=DEFAULT_CONFIG_PATH)
      ProjectNotFoundError.raise_with(path:) unless File.file?(path)
      Tomo.logger.debug("Loading configuration from #{path.inspect}")
      config_rb = File.read(path)

      new.tap do |config|
        config.path = File.expand_path(path)
        DSL::ConfigFile.new(config).instance_eval(config_rb, path.to_s, 1)
      end
    rescue StandardError, SyntaxError => e
      raise DSL::ErrorFormatter.decorate(e, path, config_rb&.lines)
    end

    attr_accessor :environments, :deploy_tasks, :setup_tasks, :hosts, :plugins, :settings, :task_filter, :path

    def initialize
      @environments = {}
      @hosts = []
      @plugins = []
      @settings = {}
      @deploy_tasks = []
      @setup_tasks = []
      @task_filter = RoleBasedTaskFilter.new
    end

    def for_environment(environment)
      validate_environment!(environment)

      dup.tap do |copy|
        copy.environments = {}
        copy.hosts = hosts_for(environment)
        copy.settings = settings_with_env_overrides(environment)
      end
    end

    def build_runtime
      validate_environment!(nil)
      plugins_registry = register_plugins

      Runtime.new(
        deploy_tasks:,
        setup_tasks:,
        plugins_registry:,
        hosts: add_log_prefixes(hosts),
        settings: { tomo_config_file_path: path }.merge(settings),
        task_filter:
      )
    end

    private

    def validate_environment!(name)
      if name.nil?
        raise_no_environment_specified unless environments.empty?
      else
        raise_unknown_environment(name) unless environments.key?(name)
      end
    end

    def hosts_for(environ)
      return hosts.dup unless environments.key?(environ)

      environments[environ].hosts
    end

    def add_log_prefixes(host_arr)
      return host_arr if host_arr.length == 1
      return host_arr unless host_arr.all? { |h| h.log_prefix.nil? }

      width = host_arr.length.to_s.length
      host_arr.map.with_index do |host, i|
        host.with_log_prefix((i + 1).to_s.rjust(width, "0"))
      end
    end

    def register_plugins
      plugins_registry = PluginsRegistry.new

      (["core"] + plugins.uniq).each do |plug|
        if plug.start_with?(".", "/")
          plug = File.expand_path(plug, File.dirname(path)) unless path.nil?
          plugins_registry.load_plugin_from_path(plug)
        else
          plugins_registry.load_plugin_by_name(plug)
        end
      end

      plugins_registry
    end

    def settings_with_env_overrides(environ)
      return settings.dup unless environments.key?(environ)

      settings.merge(environments[environ].settings)
    end

    def raise_no_environment_specified
      UnspecifiedEnvironmentError.raise_with(environments: environments.keys)
    end

    def raise_unknown_environment(environ)
      UnknownEnvironmentError.raise_with(name: environ, known_environments: environments.keys)
    end
  end
end
