# frozen_string_literal: true

#
# Collect EC2 resources
#
class EC2 < Mapper
  #
  # Returns an array of resources.
  #
  def collect
    resources = []
    image_params = {
      filters: [{ name: 'state', values: ['available'] }],
      owners: ['self']
    }
    snapshot_params = {
      owner_ids: ['self']
    }

    # global calls
    if @region == 'global'

      #
      # get_account_attributes
      #
      @client.describe_account_attributes.each do |response|
        log(response.context.operation_name)

        struct = OpenStruct.new
        struct.attributes = response.account_attributes.map(&:to_h)
        struct.type = 'account'
        struct.arn = "arn:aws:ec2::#{@account}:attributes/account_attributes"

        resources.push(struct.to_h)
      end
    end

    # regional calls
    if @region != 'global'
      #
      # get_ebs_encryption_by_default
      #
      @client.get_ebs_encryption_by_default.each do |response|
        log(response.context.operation_name)

        struct = OpenStruct.new(response.to_h)
        struct.type = 'ebs_encryption_settings'
        struct.arn = "arn:aws:ec2:#{@region}:#{@account}:settings/ebs_encryption_settings"

        resources.push(struct.to_h)
      end

      #
      # describe_instances
      #
      @client.describe_instances.each_with_index do |response, page|
        log(response.context.operation_name, page)

        # reservations
        response.reservations.each_with_index do |reservation, rpage|
          log(response.context.operation_name, 'reservations', rpage)

          # instances
          reservation.instances.each do |instance|
            struct = OpenStruct.new(instance.to_h)
            struct.type = 'instance'
            struct.arn = "arn:aws:ec2:#{@region}:#{@account}:instance/#{instance.instance_id}" # no true ARN
            struct.reservation_id = reservation.reservation_id

            # collect instance user_data
            if @options.collect_user_data
              user_data_raw = @client.describe_instance_attribute({
                                                                    attribute: 'userData',
                                                                    instance_id: instance.instance_id
                                                                  }).user_data.to_h[:value]

              # don't save non-string user_data
              if user_data_raw
                user_data = Base64.decode64(user_data_raw)

                struct.user_data = user_data if user_data.force_encoding('UTF-8').ascii_only?
              end
            end

            resources.push(struct.to_h)
          end
        end
      end

      #
      # describe_vpcs
      #
      @client.describe_vpcs.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.vpcs.each do |vpc|
          struct = OpenStruct.new(vpc.to_h)
          struct.type = 'vpc'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:vpc/#{vpc.vpc_id}" # no true ARN
          struct.flow_logs = @client
                             .describe_flow_logs({ filter: [{
                                                   name: 'resource-id',
                                                   values: [vpc.vpc_id]
                                                 }] })
                             .flow_logs.first.to_h

          resources.push(struct.to_h)
        end
      end

      #
      # describe_security_groups
      #
      @client.describe_security_groups.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.security_groups.each do |security_group|
          struct = OpenStruct.new(security_group.to_h)
          struct.type = 'security_group'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:security_group/#{security_group.group_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_network_interfaces
      #
      @client.describe_network_interfaces.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.network_interfaces.each do |network_interface|
          struct = OpenStruct.new(network_interface.to_h)
          struct.type = 'network_interface'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:network_interface/#{network_interface.network_interface_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_network_acls
      #
      @client.describe_network_acls.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.network_acls.each do |network_acl|
          struct = OpenStruct.new(network_acl.to_h)
          struct.type = 'network_acl'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:network_acl/#{network_acl.network_acl_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_subnets
      #
      @client.describe_subnets.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.subnets.each do |subnet|
          struct = OpenStruct.new(subnet.to_h)
          struct.type = 'subnet'
          struct.arn = subnet.subnet_arn

          resources.push(struct.to_h)
        end
      end

      #
      # describe_addresses
      #
      @client.describe_addresses.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.addresses.each do |address|
          struct = OpenStruct.new(address.to_h)
          struct.type = 'eip_address'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:eip_address/#{address.allocation_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_nat_gateways
      #
      @client.describe_nat_gateways.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.nat_gateways.each do |gateway|
          struct = OpenStruct.new(gateway.to_h)
          struct.type = 'nat_gateway'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:nat_gateway/#{gateway.nat_gateway_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_internet_gateways
      #
      @client.describe_internet_gateways.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.internet_gateways.each do |gateway|
          struct = OpenStruct.new(gateway.to_h)
          struct.type = 'internet_gateway'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:internet_gateway/#{gateway.internet_gateway_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_route_tables
      #
      @client.describe_route_tables.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.route_tables.each do |table|
          struct = OpenStruct.new(table.to_h)
          struct.type = 'route_table'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:route_table/#{table.route_table_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_images
      #
      @client.describe_images(image_params).each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.images.each do |image|
          struct = OpenStruct.new(image.to_h)
          struct.type = 'image'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:image/#{image.image_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_snapshots
      #
      @client.describe_snapshots(snapshot_params).each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.snapshots.each do |snapshot|
          struct = OpenStruct.new(snapshot.to_h)
          struct.type = 'snapshot'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:snapshot/#{snapshot.snapshot_id}" # no true ARN
          struct.create_volume_permissions = @client.describe_snapshot_attribute({
                                                                                   attribute: 'createVolumePermission',
                                                                                   snapshot_id: snapshot.snapshot_id
                                                                                 }).create_volume_permissions.map(&:to_h)

          resources.push(struct.to_h)
        end
      end

      #
      # describe_flow_logs
      #
      @client.describe_flow_logs.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.flow_logs.each do |flow_log|
          struct = OpenStruct.new(flow_log.to_h)
          struct.type = 'flow_log'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:flow_log/#{flow_log.flow_log_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_volumes
      #
      @client.describe_volumes.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.volumes.each do |volume|
          struct = OpenStruct.new(volume.to_h)
          struct.type = 'volume'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:volume/#{volume.volume_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_vpn_gateways
      #
      @client.describe_vpn_gateways.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.vpn_gateways.each do |gateway|
          struct = OpenStruct.new(gateway.to_h)
          struct.type = 'vpn_gateway'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:vpn_gateway/#{gateway.vpn_gateway_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_vpc_peering_connections
      #
      @client.describe_vpc_peering_connections.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.vpc_peering_connections.each do |peer|
          struct = OpenStruct.new(peer.to_h)
          struct.type = 'peering_connection'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:peering_connection/#{peer.vpc_peering_connection_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_vpc_endpoints
      #
      @client.describe_vpc_endpoints.each_with_index do |response, page|
        log(response.context.operation_name, page)

        response.vpc_endpoints.each do |point|
          struct = OpenStruct.new(point.to_h)
          struct.type = 'vpc_endpoint'
          struct.arn = "arn:aws:ec2:#{@region}:#{@account}:vpc_endpoint/#{point.vpc_endpoint_id}" # no true ARN

          resources.push(struct.to_h)
        end
      end

      #
      # describe_managed_prefix_lists
      #
      begin
        @client.describe_managed_prefix_lists.each_with_index do |response, page|
          log(response.context.operation_name, page)

          response.prefix_lists.each do |list|
            struct = OpenStruct.new(list.to_h)
            struct.type = 'prefix_list'
            struct.arn = list.prefix_list_arn

            resources.push(struct.to_h)
          end
        end
      rescue Aws::EC2::Errors::ServiceError => e
        log_error(e.code)

        raise e unless suppressed_errors.include?(e.code) && !@options.quit_on_exception
      end
    end

    resources
  end

  private

  def suppressed_errors
    %w[
      InvalidAction
    ]
  end
end
