module Origen
  class SiteConfig
    class Config
      attr_reader :path
      attr_reader :parent
      attr_reader :type
      attr_reader :values

      RESTRICTED_FROM_CENTRALIZED_VARIABLES = %w(centralized_site_config centralized_site_config_cache_dir centralized_site_config_verify_ssl)

      def initialize(path:, parent:, values: nil)
        @parent = parent
        if path == :runtime
          path = "runtime_#{object_id}"
          @type = :runtime
        elsif path.start_with?('http')
          @path = path
          @type = :centralized
        else
          @path = path
          @type = :local
        end
        @contains_centralized = false
        @loaded = false

        if values
          @values = values
          @loaded = true
        else
          @values = nil
          load
        end
      end

      def needs_refresh?
        if centralized?
          if refresh_time < 0
            false
          elsif cached?
            # If the refresh time is 0, this will always be true
            # Note the difference of time objects below will give the difference in seconds.
            (Time.now - cached_file.ctime) / 3600.0 > refresh_time
          else
            # If the cached file cannot be found, force a new fetch
            true
          end
        else
          false
        end
      end

      def refresh_time
        parent.find_val('centralized_site_config_refresh')
      end

      def cached_file
        @cached_file ||= Pathname(parent.centralized_site_config_cache_dir).join('cached_config')
      end

      def cached?
        File.exist?(cached_file)
      end

      def fetch
        def inform_user_of_cached_file
          if cached?
            puts 'Origen: Site Config: Found previously cached site config. Using the older site config.'.yellow
          else
            puts 'Origen: Site Config: No cached file found. An empty site config will be used in its place.'.yellow
          end
          puts
        end

        if centralized?
          puts "Pulling centralized site config from: #{path}"

          begin
            text = HTTParty.get(path, verify: parent.find_val('centralized_site_config_verify_ssl'))
            puts "Caching centralized site config to: #{cached_file}"

            unless Dir.exist?(cached_file.dirname)
              FileUtils.mkdir_p(cached_file.dirname)
            end
            File.open(cached_file, 'w').write(text)

          rescue SocketError => e
            puts "Origen: Site Config: Unable to connect to #{path}".red
            puts 'Origen: Site Config: Failed to retrieve centralized site config!'.red
            puts "Error from exception: #{e.message}".red

            inform_user_of_cached_file
          rescue OpenSSL::SSL::SSLError => e
            puts "Origen: Site Config: Unable to connect to #{path}".red
            puts 'Origen: Site Config: Failed to retrieve centralized site config!'.red
            puts "Error from exception: #{e.message}".red
            puts 'It looks like the error is related to SSL certification. If this is a trusted server, you can use ' \
                 "the site config setting 'centralized_site_config_verify_ssl' to disable verifying the SSL certificate.".red

            inform_user_of_cached_file
          rescue Exception => e
            # Rescue anything else to avoid any un-caught exceptions causing Origen not to boot.
            # Print lots of red so that the users are aware that there's a problem, but don't ultimately want this
            # to render Origen un-bootable
            puts "Origen: Site Config: Unexpected exception ocurred trying to either retrieve or cache the site config at #{path}".red
            puts 'Origen: Site Config: Failed to retrieve centralized site config!'.red
            puts "Class of exception:   #{e.class}".red
            puts "Error from exception: #{e.message}".red

            inform_user_of_cached_file
          end
          text
        end
      end
      alias_method :refresh, :fetch

      # Loads the site config into memory.
      # Process the site config as an ERB, if indicated to do so (.erb file extension)
      # After the initial load, any centralized site configs will be retreived (if needed), cached, and loaded.
      def load
        def read_erb(erb)
          ERB.new(File.read(erb), 0, '%<>')
        end

        if centralized?
          if !cached?
            if fetch
              # erb = ERB.new(File.read(cached_file), 0, '%<>')
              erb = read_erb(cached_file)
            else
              # There was a problem fetching the cnofig. Just use an empty string.
              # Warning message will come from #fetch
              erb = ERB.new('')
            end
          else
            # erb = ERB.new(File.read(cached_file), 0, '%<>')
            erb = read_erb(cached_file)
          end

          @values = (YAML.load(erb.result) || {})
        else
          if File.extname(path) == '.erb'
            # erb = ERB.new(File.read(path), 0, '%<>')
            erb = read_erb(path)
            @values = (YAML.load(erb.result) || {})
          else
            @values = (YAML.load_file(path) || {})
          end
        end

        unless @values.is_a?(Hash)
          puts "Origen: Site Config: The config at #{path} was not parsed as a Hash, but as a #{@values.class}".red
          puts '                     Please review the format of the this file.'.red
          puts '                     This config will not be loaded and will be replaced with an empty config.'.red
          puts
          @values = {}
        end

        if centralized?
          # check for restricted centralized config values
          RESTRICTED_FROM_CENTRALIZED_VARIABLES.each do |var|
            if @values.key?(var)
              val = @values.delete(var)
              puts 'Origen: Site Config: ' \
                   "config variable #{var} is not allowed in the centralized site config and will be removed. " \
                   "Value #{val} will not be applied!".red
            end
          end
        end

        @loaded = true
        @values
      end

      def remove_var(var)
        @values.delete(var)
      end

      def has_var?(var)
        @values.key?(var)
      end

      # Finds the value from this config, or from one of its centralized configs (if applicable)
      def find_val(val)
        @values[val]
      end
      alias_method :[], :find_val

      def loaded?
        @loaded
      end

      def local?
        type == :local
      end

      def centralized?
        type == :centralized
      end

      def runtime?
        type == :runtime
      end
    end
  end
end