# Handles parsing and validation of NZBs
class NZB
  # @return [Indexer|String] The source of the NZB
  attr_reader :provider

  # @return [String] The data contained in the NZB file
  attr_reader :data

  # Load and NZB from a file
  #
  # @param path [String] Path to an NZB
  # @return [NZB] NZB class loaded from the file
  # @raise [ArgumentError] If the input file is not acessible
  def self.from_file(path)
    raise ArgumentError, 'Given path does not exist' unless File.file? path
    new 'File', File.open(path, 'rb').read
  end

  # @param provider [Indexer|String] Where the NZB was loaded from
  # @param data [String] Data for the NZB
  # @note This function handles both raw and gzipped data strings
  def initialize(provider, data)
    @provider = provider
    @data = data
    @nzb = Nokogiri::XML(@data) { |config| config.noblanks }
  end

  # Calculates the total download size of the NZB
  #
  # @return [Integer] Total number of bytes
  def size
    @bytes ||= total_bytes
  end

  # Returns a list of files contained in the NZB
  #
  # @return [Array<String>] Files contained in the NZB
  def files
    @file_names ||= _files.map { |f| f['subject'] }.map do |name|
      name.match(/(?<=")(.+)(?=")/).to_a.first
    end
  end

  # Validates the contents of the input NZB
  #
  # @return [Boolean] Whether the NZB is valid or not
  def valid?
    check
  end

  # Returns the date the NZB was posted at
  #
  # @return [Time] Time object of when the NZB was found
  def post_date
    last_file = _files.last
    if last_file
      Time.at last_file['date'].to_i
    else
      Time.at 0
    end
  end

  # Saves the NZB to a given path
  #
  # @param path [String] Path to write the NZB to
  # @raise [ArgumentError] If the path directory doesn't exist
  # @return [Boolean] If writing the NZB was successful
  def save(path)
    raise ArgumentError, 'Given path does not exist' unless File.exist? File.dirname path
    File.open(path, 'wb') { |f| f.write @data } == @data.length
  end

  private

  def segments
    @segments ||= @nzb.search('segment')
  end

  def _files
    @_files ||= @nzb.search('file')
  end

  def total_bytes
    segments.map { |s| s['bytes'].to_i }.reduce(:+)
  end

  def file_valid?(file)
    expected_segments = file['subject'].match(/(?<=\(1\/)([0-9]+)(?=\))/).to_a.first.to_i
    expected_segments == file.search('segment').length
  end

  def check
    _files.any? && _files.map { |f| file_valid? f }.all?
  end
end
