# frozen_string_literal: true

module Noticent
  def self.configure(options = {}, &block)
    if ENV['NOTICENT_RSPEC'] == '1'
      options = options.merge(
        base_module_name: 'Noticent::Testing',
        base_dir: File.expand_path("#{File.dirname(__FILE__)}/../../testing"),
        halt_on_error: true
      )
    end

    @config = Noticent::Config::Builder.new(options, &block).build
    @config.validate!

    @config
  end

  def self.configuration
    @config || (raise Noticent::MissingConfiguration)
  end

  def self.notify(alert_name, payload)
    engine = Noticent::Dispatcher.new(@config, alert_name, payload)

    return if engine.notifiers.nil?

    engine.dispatch
  end

  class Config
    attr_reader :hooks
    attr_reader :channels
    attr_reader :scopes
    attr_reader :alerts
    attr_reader :products

    def initialize(options = {})
      @options = options
      @products = {}
    end

    def channels_by_group(group)
      return [] if @channels.nil?

      @channels.values.select { |x| x.group == group }
    end

    def channel_groups
      return [] if @channels.nil?

      @channels.values.collect(&:group).uniq
    end

    def alert_channels(alert_name)
      alert = @alerts[alert_name]
      raise ArgumentError, "no alert #{alert_name} found" if alert.nil?
      return [] if alert.notifiers.nil?

      alert.notifiers.values.collect { |notifier| channels_by_group(notifier.channel_group).uniq }.uniq.flatten
    end

    def products_by_alert(alert_name)
      alert = @alerts[alert_name]
      raise ArgumentError "no alert #{alert_name} found" if alert.nil?

      alert.products
    end

    def alerts_by_scope(scope)
      return [] if @alerts.nil?

      @alerts.values.select { |x| x.scope.name == scope }
    end

    def base_dir
      @options[:base_dir]
    end

    def base_module_name
      @options[:base_module_name]
    end

    def opt_in_provider
      @options[:opt_in_provider] || Noticent::ActiveRecordOptInProvider.new
    end

    def logger
      @options[:logger] || Logger.new(STDOUT)
    end

    def halt_on_error
      @options[:halt_on_error].nil? || false
    end

    def payload_dir
      File.join(base_dir, 'payloads')
    end

    def scope_dir
      File.join(base_dir, 'scopes')
    end

    def channel_dir
      File.join(base_dir, 'channels')
    end

    def view_dir
      File.join(base_dir, 'views')
    end

    def validate!
      # check all scopes
      scopes&.values&.each(&:validate!)
      alerts&.values&.each(&:validate!)
    end

    class Builder
      def initialize(options = {}, &block)
        @options = options
        @config = Noticent::Config.new(options)
        raise BadConfiguration, 'no OptInProvider configured' if @config.opt_in_provider.nil?

        instance_eval(&block) if block_given?

        @config.instance_variable_set(:@options, @options)
      end

      def build
        @config
      end

      def base_dir=(value)
        @options[:base_dir] = value
      end

      def base_module_name=(value)
        @options[:base_module_name] = value
      end

      def opt_in_provider=(value)
        @options[:opt_in_provider] = value
      end

      def logger=(value)
        @options[:logger] = value
      end

      def halt_on_error=(value)
        @options[:halt_on_error] = value
      end

      def hooks
        if @config.hooks.nil?
          @config.instance_variable_set(:@hooks, Noticent::Definitions::Hooks.new)
        else
          @config.hooks
        end
      end

      def product(name, &block)
        products = @config.instance_variable_get(:@products) || {}

        raise BadConfiguration, "product #{name} already defined" if products[name]

        product = Noticent::Definitions::Product.new(@config, name)
        hooks.run(:pre_product_registration, product)
        product.instance_eval(&block) if block_given?
        hooks.run(:post_product_registration, product)

        products[name] = product

        @config.instance_variable_set(:@products, products)

        product
      end

      def channel(name, group: :default, klass: nil, &block)
        channels = @config.instance_variable_get(:@channels) || {}

        raise BadConfiguration, "channel '#{name}' already defined" if channels.include? name

        channel = Noticent::Definitions::Channel.new(@config, name, group: group, klass: klass)
        hooks.run(:pre_channel_registration, channel)
        channel.instance_eval(&block) if block_given?
        hooks.run(:post_channel_registration, channel)

        channels[name] = channel

        @config.instance_variable_set(:@channels, channels)
        channel
      end

      def scope(name, payload_class: nil, &block)
        scopes = @config.instance_variable_get(:@scopes) || {}

        raise BadConfiguration, "scope '#{name}' already defined" if scopes.include? name

        scope = Noticent::Definitions::Scope.new(@config, name, payload_class: payload_class)
        scope.instance_eval(&block)

        scopes[name] = scope

        @config.instance_variable_set(:@scopes, scopes)
        scope
      end
    end
  end
end