module Dyna
  class Exporter
    include Logger::ClientHelper
    include Filterable

    class << self
      def export(ddb, options = {})
        self.new(ddb, options).export
      end
    end

    def initialize(ddb, options = {})
      @ddb = ddb
      @options = options
    end

    def export
      result = {}
      @ddb.list_tables.map do |tables|
        tables.table_names
          .reject { |name| should_skip(name) }
          .sort
          .each do |table_name|
          result[table_name] = self.class.export_table(@ddb, table_name)
        end
      end
      result
    end

    def self.table_definition(describe_table)
      {
        table_name:               describe_table.table_name,
        key_schema:               key_schema(describe_table),
        attribute_definitions:    attribute_definitions(describe_table),
        billing_mode:             describe_table.billing_mode_summary&.billing_mode,
        provisioned_throughput:   {
          read_capacity_units:    describe_table.provisioned_throughput.read_capacity_units,
          write_capacity_units:   describe_table.provisioned_throughput.write_capacity_units,
        },
        local_secondary_indexes:  local_secondary_indexes(describe_table),
        global_secondary_indexes: global_secondary_indexes(describe_table),
        stream_specification:     stream_specification(describe_table),
        scalable_targets:         scalable_targets(describe_table),
        scaling_policies:         scaling_policies(describe_table),
      }
    end

    def self.aas(aas)
      @aas = aas
    end

    private
    def self.export_table(ddb, table_name)
      describe_table = ddb.describe_table(table_name: table_name).table
      table_definition(describe_table)
    end

    def self.key_schema(table)
      table.key_schema.map do |schema|
        {
          attribute_name: schema.attribute_name,
          key_type:       schema.key_type,
        }
      end
    end

    def self.attribute_definitions(table)
      table.attribute_definitions.map do |definition|
        {
          attribute_name: definition.attribute_name,
          attribute_type: definition.attribute_type,
        }
      end
    end

    def self.global_secondary_indexes(table)
      return nil unless table.global_secondary_indexes
      table.global_secondary_indexes.map do |index|
        {
          index_name:             index.index_name,
          key_schema:             key_schema(index),
          projection:             {
            projection_type:      index.projection.projection_type,
            non_key_attributes:   index.projection.non_key_attributes,
          },
          provisioned_throughput: {
            read_capacity_units:  index.provisioned_throughput.read_capacity_units,
            write_capacity_units: index.provisioned_throughput.write_capacity_units,
          },
        }
      end
    end

    def self.local_secondary_indexes(table)
      return nil unless table.local_secondary_indexes
      table.local_secondary_indexes.map do |index|
        {
          index_name:           index.index_name,
          key_schema:           key_schema(index),
          projection:           {
            projection_type:    index.projection.projection_type,
            non_key_attributes: index.projection.non_key_attributes,
          },
        }
      end
    end

    def self.stream_specification(table)
      stream_spec = table.stream_specification
      return nil unless stream_spec
      {
        stream_enabled: stream_spec.stream_enabled,
        stream_view_type: stream_spec.stream_view_type,
      }
    end

    def self.scalable_targets(table)
      scalable_targets_by_resource_id["table/#{table.table_name}"] || []
    end

    def self.scalable_targets_by_resource_id
      return {} unless @aas
      return @scalable_targets_by_resource_id if @scalable_targets_by_resource_id

      results = []
      next_token = nil
      begin
        resp = @aas.describe_scalable_targets(service_namespace: 'dynamodb', next_token: next_token)
        resp.scalable_targets.each do |target|
          results.push(target)
        end
        next_token = resp.next_token
      end while next_token
      @scalable_targets_by_resource_id = results.group_by(&:resource_id)
    end

    def self.scaling_policies(table)
      scaling_policies_by_resource_id["table/#{table.table_name}"] || []
    end

    def self.scaling_policies_by_resource_id
      return {} unless @aas
      return @scaling_policies_by_resource_id if @scaling_policies_by_resource_id

      results = []
      next_token = nil
      begin
        resp = @aas.describe_scaling_policies(service_namespace: 'dynamodb', next_token: next_token)
        resp.scaling_policies.each do |policy|
          results.push(policy)
        end
        next_token = resp.next_token
      end while next_token
      @scaling_policies_by_resource_id = results.group_by(&:resource_id)
    end
  end
end