<commit_before>class OfxImport
  class InvalidOfxFile < RuntimeError; end
  class OfxFileHasMultipleAccounts < RuntimeError; end

  attr_reader :error, :internal_error, :cash, :bank_statement

  def initialize(file, cash = nil)
    @file = file
    @cash = cash
  end

  def run
    read_and_parse_file || (return false)
    ensure_file_has_a_single_account || (return false)
    @cash = find_or_build_cash_from_ofx_bank_account unless cash
    @bank_statement = build_bank_statement_with_items
    save_bank_statement
  end

  def recoverable?
    bank_statement.present?
  end

  private

  attr_reader :file, :parsed

  def read_and_parse_file(encoding = 'utf-8', available_encodings = %w{ us-ascii iso-8859-1 })
    content = File.open(file.path, encoding: encoding).read
    @parsed = OfxParser::OfxParser.parse(content)
    true
  rescue => error
    if (error.kind_of?(ArgumentError) || error.kind_of?(Encoding::InvalidByteSequenceError)) && (new_encoding = available_encodings.shift)
      return read_and_parse_file new_encoding, available_encodings
    end
    message = I18n.translate('activerecord.errors.models.bank_statement.ofx_file_invalid')
    @error = InvalidOfxFile.new(message)
    @internal_error = error
    false
  end

  def ensure_file_has_a_single_account
    return true if parsed.bank_accounts.length == 1
    message = I18n.translate('activerecord.errors.models.bank_statement.ofx_file_has_multiple_bank_accounts')
    @error = OfxFileHasMultipleAccounts.new(message)
    false
  end

  def ofx_statement
    ofx_bank_account.statement
  end

  def ofx_bank_account
    parsed.bank_accounts.first
  end

  def find_or_build_cash_from_ofx_bank_account
    find_cash_from_ofx_bank_account || build_cash_from_ofx_bank_account
  end

  def find_cash_from_ofx_bank_account
    number = ofx_bank_account.number<commit_msg>OFX Import : Detect file encoding through `file --mime-encoding`<commit_after>class OfxImport
  class InvalidOfxFile < RuntimeError; end
  class OfxFileHasMultipleAccounts < RuntimeError; end

  attr_reader :error, :internal_error, :cash, :bank_statement

  SUPPORTED_ENCODINGS = %w{ utf-8 iso-8859-1 us-ascii }

  def initialize(file, cash = nil)
    @file = file
    @cash = cash
  end

  def run
    read_and_parse_file || (return false)
    ensure_file_has_a_single_account || (return false)
    @cash = find_or_build_cash_from_ofx_bank_account unless cash
    @bank_statement = build_bank_statement_with_items
    save_bank_statement
  end

  def recoverable?
    bank_statement.present?
  end

  private

  attr_reader :file, :parsed

  def read_and_parse_file
    encoding = detect_encoding(file)
    content = File.open(file.path, encoding: encoding).read
    @parsed = OfxParser::OfxParser.parse(content)
    true
  rescue => error
    message = I18n.translate('activerecord.errors.models.bank_statement.ofx_file_invalid')
    @error = InvalidOfxFile.new(message)
    @internal_error = error
    false
  end

  def detect_encoding(file)
    mime_encoding = `file --mime-encoding "#{file.path}"`
    if mime_encoding =~ /:\s+([^:\s]+)/ && SUPPORTED_ENCODINGS.include?($1)
      $1
    else
      'utf-8'
    end
  end

  def ensure_file_has_a_single_account
    return true if parsed.bank_accounts.length == 1
    message = I18n.translate('activerecord.errors.models.bank_statement.ofx_file_has_multiple_bank_accounts')
    @error = OfxFileHasMultipleAccounts.new(message)
    false
  end

  def ofx_statement
    ofx_bank_account.statement
  end

  def ofx_bank_account
    parsed.bank_accounts.first
  end

  def find_or_build_cash_from_ofx_bank_account
    find_cash_from_ofx_bank_account || build_cash_from_ofx_bank_account
  end

  def find_cash_from_ofx_bank_account
    number = ofx_bank_account.number
{'fraction_non_alphanumeric': 0.07759766144034015, 'fraction_numerical': 0.006643635397289397, 'mean_word_length': 4.271708683473389, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1827819', 'n_tokens_mistral': 1319, 'n_tokens_neox': 1253, 'n_words': 237}