module ActiveShipping
  class Shipwire < Carrier
    self.retry_safe = true

    cattr_reader :name
    @@name = "Shipwire"

    URL = 'https://api.shipwire.com/exec/RateServices.php'
    SCHEMA_URL = 'http://www.shipwire.com/exec/download/RateRequest.dtd'
    WAREHOUSES = { 'CHI' => 'Chicago',
                   'LAX' => 'Los Angeles',
                   'REN' => 'Reno',
                   'VAN' => 'Vancouver',
                   'TOR' => 'Toronto',
                   'UK'  => 'United Kingdom'
                 }

    CARRIERS = ["UPS", "USPS", "FedEx", "Royal Mail", "Parcelforce", "Pharos", "Eurotrux", "Canada Post", "DHL"]

    SUCCESS = "OK"
    SUCCESS_MESSAGE = "Successfully received the shipping rates"
    NO_RATES_MESSAGE = "No shipping rates could be found for the destination address"
    REQUIRED_OPTIONS = [:login, :password].freeze

    def find_rates(origin, destination, packages, options = {})
      requires!(options, :items)
      commit(origin, destination, options)
    end

    def valid_credentials?
      location = self.class.default_location
      find_rates(location, location, Package.new(100, [5, 15, 30]),
                 :items => [{ :sku => '', :quantity => 1 }]
      )
    rescue ActiveShipping::ResponseError
      true
    rescue ActiveUtils::ResponseError => e
      e.response.code != '401'
    end

    private

    def requirements
      REQUIRED_OPTIONS
    end

    def build_request(destination, options)
      Nokogiri::XML::Builder.new do |xml|
        xml.doc.create_internal_subset('RateRequest', nil, SCHEMA_URL)
        xml.RateRequest do
          add_credentials(xml)
          add_order(xml, destination, options)
        end
      end.to_xml
    end

    def add_credentials(xml)
      xml.EmailAddress @options[:login]
      xml.Password @options[:password]
    end

    def add_order(xml, destination, options)
      xml.Order :id => options[:order_id] do
        xml.Warehouse options[:warehouse] || '00'

        add_address(xml, destination)
        Array(options[:items]).each_with_index do |line_item, index|
          add_item(xml, line_item, index)
        end
      end
    end

    def add_address(xml, destination)
      xml.AddressInfo :type => 'Ship' do
        if destination.name.present?
          xml.Name do
            xml.Full destination.name
          end
        end
        xml.Address1 destination.address1
        xml.Address2 destination.address2 unless destination.address2.blank?
        xml.Address3 destination.address3 unless destination.address3.blank?
        xml.Company destination.company unless destination.company.blank?
        xml.City destination.city
        xml.State destination.state unless destination.state.blank?
        xml.Country destination.country_code
        xml.Zip destination.zip  unless destination.zip.blank?
      end
    end

    # Code is limited to 12 characters
    def add_item(xml, item, index)
      xml.Item :num => index do
        xml.Code item[:sku]
        xml.Quantity item[:quantity]
      end
    end

    def commit(origin, destination, options)
      request = build_request(destination, options)
      save_request(request)

      response = parse( ssl_post(URL, "RateRequestXML=#{CGI.escape(request)}") )

      RateResponse.new(response["success"], response["message"], response,
                       :xml     => response,
                       :rates   => build_rate_estimates(response, origin, destination),
                       :request => last_request
      )
    end

    def build_rate_estimates(response, origin, destination)
      response["rates"].collect do |quote|
        RateEstimate.new(origin, destination, carrier_for(quote["service"]), quote["service"],
                         :service_code    => quote["method"],
                         :total_price     => quote["cost"],
                         :currency        => quote["currency"],
                         :delivery_range  => [timestamp_from_business_day(quote["delivery_min"]),
                                              timestamp_from_business_day(quote["delivery_max"])]
        )
      end
    end

    def carrier_for(service)
      CARRIERS.dup.find { |carrier| service.to_s =~ /^#{carrier}/i } || service.to_s.split(" ").first
    end

    def parse(xml)
      response = {}
      response["rates"] = []

      document = Nokogiri.XML(xml)

      response["status"] = parse_child_text(document.root, "Status")

      document.root.xpath("Order/Quotes/Quote").each do |e|
        rate = {}
        rate["method"]    = e["method"]
        rate["warehouse"] = parse_child_text(e, "Warehouse")
        rate["service"]   = parse_child_text(e, "Service")
        rate["cost"]      = parse_child_text(e, "Cost")
        rate["currency"]  = parse_child_attribute(e, "Cost", "currency")
        if delivery_estimate = e.at("DeliveryEstimate")
          rate["delivery_min"]  = parse_child_text(delivery_estimate, "Minimum").to_i
          rate["delivery_max"]  = parse_child_text(delivery_estimate, "Maximum").to_i
        end
        response["rates"] << rate
      end

      if response["status"] == SUCCESS && response["rates"].any?
        response["success"] = true
        response["message"] = SUCCESS_MESSAGE
      elsif response["status"] == SUCCESS && response["rates"].empty?
        response["success"] = false
        response["message"] = NO_RATES_MESSAGE
      else
        response["success"] = false
        response["message"] = parse_child_text(document.root, "ErrorMessage")
      end

      response
    rescue NoMethodError => e
      raise ActiveShipping::ResponseContentError.new(e, xml)
    end

    def parse_child_text(parent, name)
      if element = parent.at(name)
        element.text
      end
    end

    def parse_child_attribute(parent, name, attribute)
      if element = parent.at(name)
        element[attribute]
      end
    end
  end
end