# frozen_string_literal: true

module Rimless
  # Due to dynamic constrains on the Apache Avro schemas we need to compile our
  # schema templates to actual ready-to-consume schemas. The namespace part of
  # the schemas and cross-references to other schemas must be rendered
  # according to the dynamic namespace prefix which reflects the application
  # environment. Unfortunately we need to mess around with actual files to
  # support the Avro and AvroTurf gems.
  class AvroUtils
    attr_reader :namespace, :env

    # Create a new instance of the +AvroUtil+ class.
    #
    # @return [AvroUtil] the new instance
    def initialize
      @namespace = ENV.fetch('KAFKA_SCHEMA_SUBJECT_PREFIX',
                             Rimless.topic_prefix).tr('-', '_').gsub(/\.$/, '')
      @env = @namespace.split('.').first
    end

    # Clean and recompile all templated Avro schema files to their respective
    # output path.
    def recompile_schemas
      clear
      Dir[base_path.join('**', '*.erb')].each { |src| render_file(src) }
    end

    # Render (compile) a single Avro schema template. The given source file
    # path will serve to calculate the destination path. So even deep path'ed
    # templates will keep their hierarchy.
    #
    # @param src [String] the Avro schema template file path
    def render_file(src)
      # Convert the template path to the destination path
      dest = schema_path(src)
      # Create the deep path when not yet existing
      FileUtils.mkdir_p(File.dirname(dest))
      # Write the rendered file contents to the destination
      File.write(dest, ERB.new(File.read(src)).result(binding))
      # Check the written file for correct JSON
      validate_file(dest)
    end

    # Check the given file for valid JSON.
    #
    # @param dest [Pathname, File, IO] the file to check
    # @raise [JSON::ParserError] when invalid
    #
    # rubocop:disable Security/JSONLoad because we wrote the file contents
    def validate_file(dest)
      JSON.load(dest)
    rescue JSON::ParserError => e
      path = File.expand_path(dest.is_a?(File) ? dest.path : dest.to_s)
      prefix = "Invalid JSON detected: #{path}"
      Rimless.logger.fatal("#{prefix}\n#{e.message}")
      e.message.prepend("#{prefix} - ")
      raise e
    end
    # rubocop:enable Security/JSONLoad

    # Clear previous compiled Avro schema files to provide a clean rebuild.
    def clear
      # In a test environment, especially with parallel test execution the
      # recompiling of Avro schemas is error prone due to the deletion of the
      # configuration (output) directory. This leads to random failures due to
      # file read calls to temporary not existing files. So we just keep the
      # files and just overwrite them in place while testing.
      FileUtils.rm_rf(output_path) unless Rimless.env.test?
      FileUtils.mkdir_p(output_path)
    end

    # Return the compiled Avro schema file path for the given Avro schema
    # template.
    #
    # @param src [String] the Avro schema template file path
    # @return [Pathname] the resulting schema file path
    def schema_path(src)
      # No trailing dot on the prefix namespace directory
      prefix = env.remove(/\.$/)
      # Calculate the destination path based on the source file
      Pathname.new(src.gsub(/^#{base_path}/, output_path.join(prefix).to_s)
                      .gsub(/\.erb$/, ''))
    end

    # Return the base path of the Avro schemas on our project.
    #
    # @return [Pathname] the Avro schemas base path
    def base_path
      Rimless.configuration.avro_schema_path
    end

    # Return the path to the compiled Avro schemas. This path must be consumed
    # by the +AvroTurf::Messaging+ constructor.
    #
    # @return [Pathname] the compiled Avro schemas path
    def output_path
      Rimless.configuration.compiled_avro_schema_path
    end
  end
end