module Imap; end

module Imap::Backup
  class Serializer; end

  # An error indicating the folder is not serialized correctly
  class Serializer::FolderIntegrityError < StandardError; end

  # Checks that both the mailbox and its associated metadata file match
  class Serializer::IntegrityChecker
    # @param imap [Imap]
    # @param mbox [Mbox]
    def initialize(imap:, mbox:)
      @imap = imap
      @mbox = mbox
    end

    # Runs the integrity check
    #
    # @raise [FolderIntegrityError] if the files do not match
    # @return [void]
    def run
      Logger.logger.debug(
        "[IntegrityChecker] checking '#{imap.pathname}' against '#{mbox.pathname}'"
      )
      if !imap.valid?
        message = ".imap file '#{imap.pathname}' is corrupt"
        raise Serializer::FolderIntegrityError, message
      end

      if !mbox.exist?
        message = ".mbox file '#{mbox.pathname}' is missing"
        raise Serializer::FolderIntegrityError, message
      end

      if imap.messages.empty?
        if mbox.length.positive?
          message =
            ".imap file '#{imap.pathname}' lists no messages, " \
            "but .mbox file '#{mbox.pathname}' is not empty"
          raise Serializer::FolderIntegrityError, message
        end
        return
      end

      check_offset_ordering!
      check_mbox_length!
      check_message_starts!

      nil
    end

    private

    attr_reader :imap
    attr_reader :mbox

    def check_offset_ordering!
      offsets = imap.messages.map(&:offset)

      if offsets != offsets.sort
        message = ".imap file '#{imap.pathname}' has offset data which is out of order"
        raise Serializer::FolderIntegrityError, message
      end
    end

    def check_mbox_length!
      last = imap.messages[-1]

      expected = last.offset + last.length
      Logger.logger.debug(
        "[IntegrityChecker] mbox length is #{mbox.length}, expected length is #{expected}"
      )
      if mbox.length < expected
        message =
          ".mbox file '#{mbox.pathname}' is shorter than indicated by " \
          ".imap file '#{imap.pathname}'"
        raise Serializer::FolderIntegrityError, message
      end

      if mbox.length > expected
        message =
          ".mbox file '#{mbox.pathname}' is longer than indicated by " \
          ".imap file '#{imap.pathname}'"
        raise Serializer::FolderIntegrityError, message
      end
    end

    def check_message_starts!
      imap.messages.each do |m|
        text = mbox.read(m.offset, m.length)

        next if text.start_with?("From ")

        Logger.logger.debug(
          "[IntegrityChecker] looking for message with UID #{m.uid} " \
          "at offset #{m.offset}, " \
          "mbox starts with '#{text[0..200]}', expecting 'From '"
        )
        message =
          "Message #{m.uid} not found at expected offset #{m.offset} " \
          "in file '#{mbox.pathname}'"
        raise Serializer::FolderIntegrityError, message
      end
    end
  end
end
