require 'rubygems'
require 'nokogiri'

module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class CheckoutGateway < Gateway
      self.default_currency = 'USD'
      self.money_format = :cents

      self.supported_countries = %w[AD AT BE BG CH CY CZ DE DK EE ES FO FI FR GB GI GL GR HR HU IE IS IL IT LI LT LU LV MC MT NL NO PL PT RO SE SI SM SK SJ TR VA]
      self.supported_cardtypes = %i[visa master american_express diners_club]

      self.homepage_url = 'https://www.checkout.com/'
      self.display_name = 'Checkout.com'

      self.live_url = 'https://api.checkout.com/Process/gateway.aspx'

      ACTIONS = {
        'purchase' => '1',
        'authorize' => '4',
        'capture' => '5',
        'refund' => '2',
        'void_purchase' => '3',
        'void_authorize' => '9',
        'void_capture' => '7'
      }

      def initialize(options = {})
        requires!(options, :merchant_id, :password)
        super
      end

      def purchase(amount, payment_method, options)
        commit('purchase', amount, options) do |xml|
          add_credentials(xml, options)
          add_invoice(xml, amount, options)
          add_track_id(xml, options[:order_id] || generate_unique_id)
          add_payment_method(xml, payment_method)
          add_billing_info(xml, options)
          add_shipping_info(xml, options)
          add_user_defined_fields(xml, options)
          add_other_fields(xml, options)
        end
      end

      def authorize(amount, payment_method, options)
        commit('authorize', amount, options) do |xml|
          add_credentials(xml, options)
          add_invoice(xml, amount, options)
          add_track_id(xml, options[:order_id] || generate_unique_id)
          add_payment_method(xml, payment_method)
          add_billing_info(xml, options)
          add_shipping_info(xml, options)
          add_user_defined_fields(xml, options)
          add_other_fields(xml, options)
        end
      end

      def capture(amount, authorization, options = {})
        commit('capture', amount, options) do |xml|
          add_credentials(xml, options)
          add_reference(xml, authorization)
          add_invoice(xml, amount, options)
          add_user_defined_fields(xml, options)
          add_other_fields(xml, options)
        end
      end

      def void(authorization, options = {})
        _, _, orig_action, amount, currency = split_authorization(authorization)
        commit("void_#{orig_action}") do |xml|
          add_credentials(xml, options)
          add_invoice(xml, amount.to_i, options.merge(currency:))
          add_reference(xml, authorization)
        end
      end

      def refund(amount, authorization, options = {})
        commit('refund') do |xml|
          add_credentials(xml, options)
          add_invoice(xml, amount.to_i, options)
          add_reference(xml, authorization)
        end
      end

      def verify(credit_card, options = {})
        MultiResponse.run(:use_first_response) do |r|
          r.process { authorize(100, credit_card, options) }
          r.process(:ignore_result) { void(r.authorization, options) }
        end
      end

      private

      def add_credentials(xml, options)
        xml.merchantid_ @options[:merchant_id]
        xml.password_ @options[:password]
      end

      def add_invoice(xml, amount, options)
        xml.bill_amount_ amount(amount)
        xml.bill_currencycode_ options[:currency] || currency(amount)
      end

      def add_payment_method(xml, payment_method)
        xml.bill_cardholder_ payment_method.name
        xml.bill_cc_ payment_method.number
        xml.bill_expmonth_ format(payment_method.month, :two_digits)
        xml.bill_expyear_ format(payment_method.year, :four_digits)
        xml.bill_cvv2_ payment_method.verification_value if payment_method.verification_value?
      end

      def add_billing_info(xml, options)
        if options[:billing_address]
          xml.bill_address_ options[:billing_address][:address1]
          xml.bill_city_    options[:billing_address][:city]
          xml.bill_state_   options[:billing_address][:state]
          xml.bill_postal_  options[:billing_address][:zip]
          xml.bill_country_ options[:billing_address][:country]
          xml.bill_phone_   options[:billing_address][:phone]
        end
      end

      def add_shipping_info(xml, options)
        if options[:shipping_address]
          xml.ship_address_  options[:shipping_address][:address1]
          xml.ship_address2_ options[:shipping_address][:address2]
          xml.ship_city_     options[:shipping_address][:city]
          xml.ship_state_    options[:shipping_address][:state]
          xml.ship_postal_   options[:shipping_address][:zip]
          xml.ship_country_  options[:shipping_address][:country]
          xml.ship_phone_    options[:shipping_address][:phone]
        end
      end

      def add_user_defined_fields(xml, options)
        xml.udf1_ options[:udf1]
        xml.udf2_ options[:udf2]
        xml.udf3_ options[:udf3]
        xml.udf4_ options[:udf4]
        xml.udf5_ options[:udf5]
      end

      def add_other_fields(xml, options)
        xml.bill_email_ options[:email]
        xml.bill_customerip_ options[:ip]
        xml.merchantcustomerid_ options[:customer]
        xml.descriptor_name options[:descriptor_name]
        xml.descriptor_city options[:descriptor_city]
      end

      def add_reference(xml, authorization)
        transid, trackid, = split_authorization(authorization)
        xml.transid transid
        add_track_id(xml, trackid)
      end

      def add_track_id(xml, trackid)
        xml.trackid(trackid) if trackid
      end

      def commit(action, amount = nil, options = {}, &)
        response = parse_xml(ssl_post(live_url, build_xml(action, &)))
        Response.new(
          (response[:responsecode] == '0'),
          (response[:result] || response[:error_text] || 'Unknown Response'),
          response,
          authorization: authorization_from(response, action, amount, options),
          test: test?
        )
      end

      def build_xml(action)
        Nokogiri::XML::Builder.new do |xml|
          xml.request do
            xml.action_ ACTIONS[action]
            yield xml
          end
        end.to_xml
      end

      def parse_xml(xml)
        response = {}

        Nokogiri::XML(CGI.unescapeHTML(xml)).xpath('//response').children.each do |node|
          if node.text?
            next
          elsif node.elements.size == 0
            response[node.name.downcase.to_sym] = node.text
          else
            node.elements.each do |childnode|
              name = "#{node.name.downcase}_#{childnode.name.downcase}"
              response[name.to_sym] = childnode.text
            end
          end
        end

        response
      end

      def authorization_from(response, action, amount, options)
        currency = options[:currency] || currency(amount)
        [response[:tranid], response[:trackid], action, amount, currency].join('|')
      end

      def split_authorization(authorization)
        transid, trackid, action, amount, currency = authorization.split('|')
        [transid, trackid, action, amount, currency]
      end
    end
  end
end
