##
# Base for fetching data from Paypal NVP API
#   See docs: https://developer.paypal.com/docs/classic/api/
##
module RubyPaypalNvp
  module Fetcher
    class Base
      ##
      # Input options:
      #   date_from
      #   date_to
      #   currency
      #   subject
      #   transaction_class
      #
      # rubocop:disable Metrics/AbcSize
      def initialize(opts)
        @start_date = opts.fetch(:date_from, Time.zone.now).beginning_of_day.utc.iso8601
        @end_date = opts.fetch(:date_to, Time.zone.now).end_of_day.utc.iso8601
        @currency = opts.fetch(:currency, 'CZK')
        @subject = opts[:subject] || RubyPaypalNvp.configuration.subject
        @transaction_class = opts.fetch(:transaction_class, 'BalanceAffecting')
        @resulting_hash = default_hash
      end
      # rubocop:enable Metrics/AbcSize

      def call
        result = load_response
        process_loaded_data(result)
      rescue NoMethodError
        raise "Error processing #{self.class} for #{@subject}"
      end

      def self.call(options)
        new(options).call
      end

      def load_api_response(options)
        uri = URI(RubyPaypalNvp.configuration.api_url)
        req = Net::HTTP::Post.new(uri)
        req.set_form_data(options)
        res = Net::HTTP.start(uri.hostname, uri.port,
                              use_ssl: uri.scheme == 'https') do |http|
          http.open_timeout = 6000
          http.read_timeout = 6000
          http.request(req)
        end
        pretty_json(res.body)
      end

      private

      # rubocop:disable Metrics/AbcSize
      # rubocop:disable Metrics/MethodLength
      def parse(body, options = {})
        @resulting_hash[:values].tap do |parsed_response|
          param_type = self.class::RESPONSE_PARAMS.first
          all_for_params_type = body.select { |k, _v| k.start_with?(param_type) }
          all_for_params_type.each_with_index do |(key, value), index|
            real_index = index + options[:increment]
            parsed_response[real_index] ||= {}
            parsed_response[real_index] = parsed_response[real_index]
                                          .merge(param_type.to_s => value)
            key = key.gsub(param_type, '')
            self.class::RESPONSE_PARAMS.drop(1).each do |attribute_name|
              pair = { attribute_name => body["#{attribute_name}#{key}"] }
              parsed_response[real_index] = parsed_response[real_index].merge pair
            end
          end
        end
      end
      # rubocop:enable Metrics/AbcSize
      # rubocop:enable Metrics/MethodLength

      # Implement in each child to save/send data
      def process_loaded_data(_result)
        true
      end

      def result_with_meta(options = {})
        @resulting_hash[:meta]['timestamp'] = options[:timestamp]
        @resulting_hash[:meta]['start_date'] = @start_date
        @resulting_hash[:meta]['end_date'] = @end_date
        @resulting_hash[:meta]['subject'] = @subject
        @resulting_hash[:meta]['currency_code'] = @currency
        @resulting_hash
      end

      def request_options(_options = {})
        {
          method: api_method,
          version: RubyPaypalNvp.configuration.version,
          user: RubyPaypalNvp.configuration.user,
          pwd: RubyPaypalNvp.configuration.password,
          signature: RubyPaypalNvp.configuration.signature,
          subject: @subject
        }
      end

      # Needs to be specific in each child
      def api_method
        ''
      end

      def pretty_json(body)
        Hash[URI.decode_www_form(body)]
      end

      def default_hash
        { meta: {}, values: {} }
      end
    end
  end
end