require 'mechanize'
require 'logger'

module Bankscrap
  class Bank
    WEB_USER_AGENT = 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, ' \
                     'like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19'.freeze
    attr_accessor :headers, :accounts, :cards, :loans, :investments

    REQUIRED_CREDENTIALS = %i[user password].freeze

    class MissingCredential < ArgumentError; end

    def initialize(credentials = {})
      # Assign required credentials as env vars.
      # If empty, use ENV vars as fallback:
      # BANKSCRAP_MY_BANK_USER, BANKSCRAP_MY_BANK_PASWORD, etc.
      self.class::REQUIRED_CREDENTIALS.each do |field|
        value = credentials.with_indifferent_access[field] || ENV["#{env_vars_prefix}_#{field.upcase}"]
        raise MissingCredential, "Missing credential: '#{field}'" if value.blank?
        instance_variable_set("@#{field}", value)
      end

      initialize_http_client

      # Bank adapters should use the yield block to do any required processing of credentials
      yield if block_given?

      login
      @accounts = fetch_accounts

      # Not all the adapters have support for investments, loans or cards
      @investments = fetch_investments if respond_to?(:fetch_investments)
      @loans = fetch_loans if respond_to?(:fetch_loans)
      @cards = fetch_cards if respond_to?(:fetch_cards)
    end

    # Interface method placeholders

    def login
      raise "#{self.class} should implement a login method"
    end

    def fetch_accounts
      raise "#{self.class} should implement a fetch_account method"
    end

    def fetch_transactions_for(*)
      raise "#{self.class} should implement a fetch_transactions method"
    end

    def account_with_iban(iban)
      accounts.find do |account|
        account.iban.delete(' ') == iban.delete(' ')
      end
    end

    private

    def get(url, params: [], referer: nil)
      @http.get(url, params, referer, @headers).body
    end

    def post(url, fields: {})
      @http.post(url, fields, @headers).body
    end

    def put(url, fields: {})
      @http.put(url, fields, @headers).body
    end

    # Sets temporary HTTP headers, execute a code block
    # and resets the headers
    def with_headers(tmp_headers)
      current_headers = @headers
      add_headers(tmp_headers)
      yield
    ensure
      add_headers(current_headers)
    end

    def add_headers(headers)
      @headers.merge! headers
      @http.request_headers = @headers
    end

    def initialize_cookie(url)
      @http.get(url).body
    end

    def initialize_http_client
      @http = Mechanize.new do |mechanize|
        mechanize.user_agent = WEB_USER_AGENT
        mechanize.agent.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        mechanize.log = Logger.new(STDOUT) if Bankscrap.debug

        if Bankscrap.proxy
          mechanize.set_proxy Bankscrap.proxy[:host], Bankscrap.proxy[:port]
        end
      end

      @headers = {}
    end

    def log(msg)
      puts msg if Bankscrap.log
    end

    # Prefix for env vars used to store credentials
    def env_vars_prefix
      self.class.parent.name.underscore.upcase.tr('/', '_')
    end
  end
end
