# frozen_string_literal: true

#
# Collect IAM resources
#
class IAM < Mapper
  #
  # Returns an array of resources.
  #
  def collect
    resources = []

    #
    # get_account_authorization_details
    #   list_mfa_devices
    #   list_ssh_public_keys
    #
    opts = {
      filter: %w[User Role Group LocalManagedPolicy AWSManagedPolicy]
    }
    @client.get_account_authorization_details(opts).each_with_index do |response, page|
      log(response.context.operation_name, page)

      # users
      response.user_detail_list.each do |user|
        struct = OpenStruct.new(user.to_h)
        struct.type = 'user'
        struct.mfa_devices = @client.list_mfa_devices({ user_name: user.user_name }).mfa_devices.map(&:to_h)
        struct.ssh_keys = @client.list_ssh_public_keys({ user_name: user.user_name }).ssh_public_keys.map(&:to_h)
        struct.user_policy_list = if user.user_policy_list
                                    user.user_policy_list.map do |p|
                                      {
                                        policy_name: p.policy_name,
                                        policy_document: p.policy_document.parse_policy
                                      }
                                    end
                                  end

        resources.push(struct.to_h)
      end

      # groups
      response.group_detail_list.each do |group|
        struct = OpenStruct.new(group.to_h)
        struct.type = 'group'
        struct.group_policy_list = if group.group_policy_list
                                     group.group_policy_list.map do |p|
                                       {
                                         policy_name: p.policy_name,
                                         policy_document: p.policy_document.parse_policy
                                       }
                                     end
                                   end

        resources.push(struct.to_h)
      end

      # roles
      response.role_detail_list.each do |role|
        struct = OpenStruct.new(role.to_h)
        struct.type = 'role'
        struct.assume_role_policy_document = role.assume_role_policy_document.parse_policy
        struct.role_policy_list = if role.role_policy_list
                                    role.role_policy_list.map do |p|
                                      {
                                        policy_name: p.policy_name,
                                        policy_document: p.policy_document.parse_policy
                                      }
                                    end
                                  end

        resources.push(struct.to_h)
      end

      # polices
      response.policies.each do |policy|
        struct = OpenStruct.new(policy.to_h)
        struct.type = 'policy'
        struct.policy_version_list = if policy.policy_version_list
                                       policy.policy_version_list.map do |p|
                                         {
                                           version_id: p.version_id,
                                           document: p.document.parse_policy,
                                           is_default_version: p.is_default_version,
                                           create_date: p.create_date
                                         }
                                       end
                                     end

        resources.push(struct.to_h)
      end
    end

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

      # instance_profiles
      response.instance_profiles.each do |profile|
        struct = OpenStruct.new(profile.to_h)
        struct.type = 'instance_profile'
        struct.arn = profile.arn
        struct.roles = []

        profile.roles&.each do |role|
          role.assume_role_policy_document = role.assume_role_policy_document.parse_policy
          struct.roles.push(role.to_h)
        end

        resources.push(struct.to_h)
      end
    end

    #
    # get_account_password_policy
    #
    begin
      @client.get_account_password_policy.each do |response|
        log(response.context.operation_name)

        struct = OpenStruct.new(response.password_policy.to_h)
        struct.type = 'password_policy'
        struct.arn = "arn:aws:iam::#{@account}:account_password_policy/global"

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

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

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

      struct = OpenStruct.new(response.summary_map)
      struct.type = 'account_summary'
      struct.arn = "arn:aws:iam::#{@account}:account_summary/global"

      resources.push(struct.to_h)
    end

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

      response.server_certificate_metadata_list.each do |cert|
        struct = OpenStruct.new(cert)
        struct.type = 'server_certificate'
        struct.arn = cert.arn

        resources.push(struct.to_h)
      end
    end

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

      response.virtual_mfa_devices.each do |mfa_device|
        struct = OpenStruct.new(mfa_device.to_h)
        struct.type = 'virtual_mfa_device'
        struct.arn = mfa_device.serial_number

        resources.push(struct.to_h)
      end
    end

    #
    # generate_credential_report
    #
    unless @options.skip_credential_report
      status = 'STARTED'
      interval = 5

      # wait for report to generate
      while status != 'COMPLETE'
        @client.generate_credential_report.each do |response|
          log(response.context.operation_name)
          status = response.state
        end

        sleep interval unless status == 'COMPLETE'
      end
    end

    #
    # get_credential_report
    #
    begin
      @client.get_credential_report.each do |response|
        log(response.context.operation_name)

        struct = OpenStruct.new
        struct.type = 'credential_report'
        struct.arn = "arn:aws:iam::#{@account}:credential_report/global"
        struct.content = CSV.parse(response.content, headers: :first_row).map(&:to_h)
        struct.report_format = response.report_format
        struct.generated_time = response.generated_time

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

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

    resources
  end

  private

  # not an error
  def suppressed_errors
    %w[
      ReportNotPresent
      NoSuchEntity
    ]
  end
end
