# DUCK: This should be either a model by itself or remove the whole crap and live with billing_address_ prefix
module Account::BillingAddress
  extend ActiveSupport::Concern

  class AddressFormatError < StandardError
    attr_reader :object

    def initialize(object)
      @object = object
      super('Billing address is not correctly set')
    end
  end

  included do
    validates :billing_address_name,     presence: { if: :billing_address? }
    validates :billing_address_address1, presence: { if: :billing_address? }
    validates :billing_address_city,     presence: { if: :billing_address? }
    validates :billing_address_country,  presence: { if: :billing_address? }

    after_validation :copy_errors_to_billing_address
  end

  class Address

    attr_reader :data
    attr_accessor :name, :address1, :address2, :city, :country, :state, :zip, :phone, :errors

    # We alias line* and address to make it quack like a
    # ThreeScale::Address (needed in invoice model)
    alias line1 address1
    alias line2 address2

    def initialize(data= {})
      data.each{ | key, value | send("#{key}=", value) }
      @data = data

      @errors = ActiveModel::Errors.new self
    end

    ATTRIBUTES = %i[ company address phone_number city country state zip ]

    def to_hash
      ATTRIBUTES.map do |attribute|
        [attribute, self[attribute]]
      end.to_h
    end

    def to_xml(options = {})
      xml = options[:builder] || ThreeScale::XML::Builder.new
      xml.__send__(options.fetch(:root, :billing_address)) do |xml|
        to_hash.each do |attr, value|
          xml.__send__(attr, value)
        end
      end

      xml.to_xml
    end

    # Quacking like Hash makes BillingAddress compatible with
    # ActiveMerchant. See AuthorizeNetCimGateway#add_address for interface.
    #
    def [](key)
      case key
      when :company then self.name
      when :address then [ self.address1, self.address2 ].compact.join("\n")
      when :phone_number then self.phone
      when :city, :country, :state, :zip then self.send(key)
      end
    end

  def to_s
    attrs = %i[address phone city country state zip]
    attrs.map{|attr| self[attr].presence}.compact.join(', ')
  end

  end

  # Somehow this is different than <tt>#billing_address?</tt> and it's used
  # when a payment gateway needs a billing address before entering CC data.
  def has_billing_address?
    !billing_address_name.nil?
  end

  def billing_address?
    !@billing_address_set.nil?
  end

  def billing_address
    @billing_address ||= Address.new({
      name:     self.billing_address_name || self.org_name,
      address1: self.billing_address_address1,
      address2: self.billing_address_address2,
      country:  self.billing_address_country || default_country,
      city:     self.billing_address_city,
      state:    self.billing_address_state,
      zip:      self.billing_address_zip,
      phone:    self.billing_address_phone || self.telephone_number
    })
  end

  def billing_address=(address)
    raise Account::BillingAddress::AddressFormatError, self unless address.respond_to?(:each)
    @billing_address_set = true
    address.each do | key, value |
      send("billing_address_#{key}=", value)
    end
    @billing_address= Address.new(address)
  end

  def delete_billing_address
    [ :address1, :address2, :name, :phone, :city, :country, :state, :zip ].each do |attr|
      send("billing_address_#{attr}=", nil)
    end
    @billing_address = nil
    @billing_address_set = nil
  end

  def copy_errors_to_billing_address
    [:name, :address1, :city, :country].each do | field |
      self.errors["billing_address_#{field}"].each do | error |
        self.billing_address.errors.add field, error
      end
    end
  end

  def reload(*)
    super.tap do
      @billing_address_set = nil
      @billing_address = nil
    end
  end

  private

  def default_country
    self.country.code if self.country
  end
end
