modules/appeals_api/app/services/appeals_api/central_mail_updater.rb
# frozen_string_literal: true

module AppealsApi
  class CentralMailUpdater
    MAX_UUIDS_PER_REQUEST = 100

    ERROR_UNIDENTIFIED_MAIL = %w[
      Unidentified Mail: We could not associate part or all of this submission with a Veteran.
      Please verify the identifying information and resubmit.
    ].join(' ')

    CENTRAL_MAIL_ERROR_STATUSES = ['Error'].freeze

    CENTRAL_MAIL_STATUS_ATTRIBUTES = {
      'Received' => { status: 'submitted' }, # received upstream of our API
      'In Process' => { status: 'processing' }, # indicates vba intake
      'Success' => { status: 'success' }, # received by the centralized mail portal
      'VBMS Complete' => { status: 'complete' }, # document package received by vbms
      'Error' => { status: 'error', code: 'DOC202' }
    }.freeze

    CENTRAL_MAIL_STATUSES = CENTRAL_MAIL_STATUS_ATTRIBUTES.to_a.map { |_, x| x.fetch(:status) }.freeze

    CENTRAL_MAIL_STATUS = Struct.new(:id, :_status, :_error_message, :packets) do
      # ex. packets => [{"veteranId"=>"123456789", "status"=>"Complete", "completedReason"=>"DownloadConfirmed",
      #                  "transactionDate"=>"2022-05-06"}]
      delegate :present?, to: :id

      def status
        if _status == 'Complete'
          packet_results_status
        else
          _status
        end
      end

      def error_message
        if unidentifiable_mail_present?
          ERROR_UNIDENTIFIED_MAIL
        else
          _error_message
        end
      end

      def error?
        status.in?(CENTRAL_MAIL_ERROR_STATUSES) && error_message
      end

      def packet_results_status
        if unidentifiable_mail_present?
          'Error'
        else
          'VBMS Complete'
        end
      end

      def unidentifiable_mail_present?
        Array(packets).any? { |p| p['completedReason'] == 'UnidentifiableMail' }
      end
    end

    def call(appeals)
      return if appeals.empty?

      central_mail_response = CentralMail::Service.new.status(appeals.pluck(:id))
      unless central_mail_response.success?
        Rails.logger.warn('Error getting status from Central Mail API.',
                          { central_mail_status: central_mail_response.status,
                            central_mail_body: central_mail_response.body })
        raise Common::Exceptions::BadGateway
      end

      update_appeals!(appeals, central_mail_response)
    end

    private

    def update_appeals!(appeals, central_mail_response)
      parse_central_mail_response(central_mail_response).each do |status|
        appeal = appeals.find { |a| a.id == status.id }
        next unless appeal

        update_appeal_status!(appeal, status)
      end
    end

    def parse_central_mail_response(raw_response)
      JSON.parse(raw_response.body).flatten.map do |hash|
        CENTRAL_MAIL_STATUS.new(*hash.values_at('uuid', 'status', 'errorMessage', 'packets'))
      end
    end

    def update_appeal_status!(appeal, central_mail_status)
      attributes = CENTRAL_MAIL_STATUS_ATTRIBUTES.fetch(central_mail_status.status) do
        Rails.logger.warn('Unknown status value from Central Mail API.',
                          { central_mail_status: central_mail_status.status })
        raise Common::Exceptions::BadGateway
      end

      if central_mail_status.error?
        attributes = attributes.merge(detail: "Downstream status: #{central_mail_status.error_message}")
      end

      begin
        appeal.update_status!(**attributes)
      rescue => e
        log_exception e, appeal, central_mail_status.status
      end
    end

    def log_exception(e, appeal, status)
      details = {
        class: self.class.to_s,
        detail: 'Error when trying to update appeal status',
        appeal_type: appeal.class.to_s,
        appeal_id: appeal.id,
        appeal_status: appeal.status,
        attempted_status: status,
        exception: e.class.to_s,
        exception_message: e.message
      }

      Rails.logger.warn('Error updating appeal status', details)

      AppealsApi::Slack::Messager.new(details).notify!
    end
  end
end
