require 'soap/wsdlDriver' 
require 'soap/header/simplehandler'
require 'stringio'

module ActiveMerchant #:nodoc:
  module Billing #:nodoc:
    module EwayManaged
      class ProxyBase < EwayBase::Proxy
        def wdsl
          "https://www.eway.com.au/gateway/ManagedPaymentService/test/managedCreditCardPayment.asmx?WSDL"
        end

        def header(eway_customer_id, username, password)
          EwayManagedHeader.new(eway_customer_id, username, password)
        end
      end

      class EwayManagedHeader < SOAP::Header::SimpleHandler
        def initialize(eway_customer_id, username, password)
          super(XSD::QName.new('https://www.eway.com.au/gateway/managedpayment', 'eWAYHeader'))
          @item = { :eWAYCustomerID => eway_customer_id, :Username => username, :Password => password }
        end

        def on_simple_outbound
          @item if @item
        end
      end

      class Customer < ProxyBase
        attr_accessor :options

        def initialize(attributes = {}, options = {})
          self.fields = [ :id, :ref, :title, :first_name, :last_name, :company, :job_desc, :email, :address, :suburb, :state, :post_code, :country, :phone, :mobile, :fax, :url, :comments ]
          self.options = options
          super(attributes)
        end

        def credit_card
          @credit_card
        end

        def credit_card=(credit_card)
          @credit_card = credit_card
        end

        def save(options = {})
          if id
            update(options)
          else
            create(options)
          end
          return true
        rescue
          return false
        end

        def save!(options = {})
          if id
            update(options)
          else
            create(options)
          end
        end

        def create(options = {})
          self.credit_card.require_verification_value = false
          raise StandardError.new(self) unless self.valid?
          raise StandardError.new(self.credit_card) unless self.credit_card.valid?
          self.credit_card.year = self.credit_card.year.to_s[2..3]

          options = self.options.merge(options)
          res = driver(options[:login], options[:username], options[:password]).CreateCustomer(prepared_attributes).createCustomerResult
          self.id = res.to_i
        end

        def update(options = {})
          self.credit_card.require_verification_value = false
          raise StandardError.new(self) unless self.valid?
          raise StandardError.new(self.credit_card) unless self.credit_card.valid?
          self.credit_card.year = self.credit_card.year.to_s[2..3]

          options = self.options.merge(options)
          res = driver(options[:login], options[:username], options[:password]).UpdateCustomer(prepared_attributes).updateCustomerResult
          return res == "true"
        end

        def self.query(id, options = {})
          proxy = ProxyBase.new
          response = proxy.driver(options[:login], options[:username], options[:password]).QueryCustomer(:managedCustomerID => id).queryCustomerResult

          customer = Customer.new({}, options)

          customer.fields.each do |field|
            case(field)
            when :id
              camel_key = "managedCustomerID"
            when :url
              camel_key = "customerURL"
            when :phone
              camel_key = "customerPhone1"
            when :mobile
              camel_key = "customerPhone2"
            else
              camel_key = "customer" + ProxyBase.camelize(field)
            end
            customer.send("#{field.to_s}=", response.send(camel_key))
          end

          ccName = response.cCName.split(' ')
          customer.credit_card = ActiveMerchant::Billing::CreditCard.new(
            :number => response.cCNumber,
            :month => response.cCExpiryMonth,
            :year => response.cCExpiryYear,
            :first_name => ccName.shift,
            :last_name => ccName.join(' ')
          )

          customer
        end

        def validate
          errors.add :first_name, 'is required' if self.first_name.blank?
          errors.add :last_name, 'is required' if self.last_name.blank?
          errors.add :email, 'is required' if self.email.blank?
        end

        def prepared_attributes(attributes = nil)
          attributes ||= @attributes
          tmp = {}
          self.fields.each do |key|
            case(key)
            when :id
              camel_key = "managedCustomerID"
            when :ref
              camel_key = "CustomerRef"
            when :url
              camel_key = "URL"
            else
              camel_key = ProxyBase.camelize(key)
            end

            tmp[camel_key.to_s] = attributes[key].to_s
          end
          tmp["Title"] = "Mr."
          tmp["Country"] = "au"

          # Now go through the Credit Card object
          tmp['CCNumber'] = self.credit_card.number
          tmp['CCNameOnCard'] = self.credit_card.first_name + " " + self.credit_card.last_name
          tmp['CCExpiryMonth'] = self.credit_card.month
          tmp['CCExpiryYear'] = self.credit_card.year

          tmp
        end
      end

      class CustomerResponse < EwayBase::Response
        attr_accessor :customer_id, :customer

        def initialize(soap_obj)
          super
          self.customer_id = soap_obj.rebillCustomerID == "0" ? nil : soap_obj.rebillCustomerID

          # Try to fill the customer object
          self.customer = Customer.new
          self.customer.id = self.customer_id

          self.customer.fields.each do |key|
            case(key)
            when(:url)
              method = "customerURL"
            else
              method = "customer" + ProxyBase.camelize(key)
            end

            self.customer.send("#{key}=", soap_obj.send(method)) if soap_obj.respond_to?(method)
          end
        end
      end

      class Payment < ProxyBase
        def initialize(attributes = {})
          self.fields = [ :id, :customer_id, :amount, :invoice_reference, :invoice_description ]
          super
        end

        def self.query(customer_id, options = {})
          proxy = ProxyBase.new
          res = proxy.driver(options[:login], options[:username], options[:password]).QueryPayment(:managedCustomerID => customer_id).queryPaymentResult
          payments = []
          res.managedTransaction.each do |t|
            payments << PaymentQueryResponse.new(t)
          end
          payments
        end

        def query(options = {})
          Payment.query(self.customer_id)
        end

        def process(options = {})
          begin
            res = driver(options[:login], options[:username], options[:password]).ProcessPayment(prepared_attributes).ewayResponse
            return PaymentResponse.new(res)
          rescue SOAP::FaultError => message
            response = EwayManaged::PaymentResponse.new
            response.status = false
            response.error = message
            response.return_amount = self.amount
            return response
          end
        end

        def prepared_attributes(attributes = nil)
          attributes ||= @attributes
          tmp = {}
          self.fields.each do |key|
            case(key)
            when :customer_id
              camel_key = "managedCustomerID"
            else
              camel_key = ProxyBase.camelize(key, false)
            end
            tmp[camel_key] = attributes[key]
          end

          tmp
        end

        def validate
          errors.add :customer_id, 'is required' if self.customer_id.blank?
          errors.add :amount, 'is required' if self.amount.blank?
          errors.add :invoice_reference, 'is required' if self.invoice_reference.blank?
          errors.add :amount, 'must be a number' unless self.amount.is_a(Number) 
        end
      end

      class PaymentResponse < EwayBase::Response
        attr_accessor :status, :transaction_number, :return_amount, :auth_code, :error

        def initialize(soap_obj = nil)
          if soap_obj
            self.status = soap_obj.ewayTrxnStatus == "True"
            self.transaction_number = soap_obj.ewayTrxnNumber.to_i
            self.return_amount = soap_obj.ewayReturnAmount.to_i
            self.auth_code = soap_obj.ewayAuthCode
            self.error = soap_obj.ewayTrxnError
          end
        end
      end

      class PaymentQueryResponse < EwayBase::Response
        attr_accessor :amount, :success, :response, :date, :transaction_number

        def initialize(soap_obj = nil)
          if soap_obj
            self.amount = soap_obj.totalAmount.to_i.to_f / 100
            self.success = soap_obj.result == "1"
            self.response = soap_obj.responseText
            self.date = DateTime.strptime(soap_obj.transactionDate, "%Y-%m-%dT%H:%M:%S%Z")
            self.transaction_number = soap_obj.ewayTrxnNumber.to_i
          end
        end
      end
    end
  end
end