module Smess
  class Ipx < Output
    include Smess::Logging

    attr_accessor :sms_url, :username, :password, :shortcode, :account_name, :service_name, :service_meta_data_verizon, :service_meta_data_t_mobile_us
    def validate_config
      @sms_url                       = config.fetch(:sms_url)
      @username                      = config.fetch(:username)
      @password                      = config.fetch(:password)
      @shortcode                     = config.fetch(:shortcode)
      @account_name                  = config.fetch(:account_name)
      @service_name                  = config.fetch(:service_name)
      @service_meta_data_verizon     = config.fetch(:service_meta_data_verizon, "")
      @service_meta_data_t_mobile_us = config.fetch(:service_meta_data_t_mobile_us, "")

      @results = []
      @endpoint = sms_url
      @credentials = {
        name: username,
        pass: password
      }
    end

    def deliver
      set_originator(sms.originator)
      perform_operator_adaptation(sms.to)

      parts.each_with_index do |part, i|
        populate_soap_body(part, i)
        results << send_one_sms

        # halt and use fallback on error...
        if last_result_was_error
          logger.info "IPX_ERROR: #{results.last}"
          return fallback_to_twilio || results.first
        end
      end

      # we don't actually return the status for any of additional messages which is cheating
      results.first
    end

    private

    attr_reader :sms
    attr_accessor :results

    def soap_body
      @soap_body ||= {
        "correlationId" => Time.now.strftime('%Y%m%d%H%M%S') + sms.to,
        "originatingAddress" => shortcode,
        "originatorTON" => "0",
        "destinationAddress" => sms.to,
        "userData" => "",
        "userDataHeader" => "#NULL#",
        "DCS" => "-1",
        "PID" => "-1",
        "relativeValidityTime" => "-1",
        "deliveryTime" => "#NULL#",
        "statusReportFlags" => "1", # 1
        "accountName" => account_name,
        "tariffClass" => "USD0", # needs to be extracted and variable per country
        "VAT" => "-1",
        "referenceId" => "#NULL#",
        "serviceName" =>  service_name,
        "serviceCategory" => "#NULL#",
        "serviceMetaData" => "#NULL#",
        "campaignName" => "#NULL#",
        "username" => username,
        "password" => password
      }
    end


    def soap_client
      Savon.configure do |config|
        config.log_level = :info
        config.raise_errors = false
      end

      endpoint = @endpoint
      mm7ns = wsdl_namespace
      credentials = @credentials

      client = Savon::Client.new do |wsdl, http|
        wsdl.endpoint = endpoint
        wsdl.namespace = mm7ns

        http.open_timeout = 15
        http.read_timeout = 60 # Won't set read timeout to 10 minutes!! (IPX are crazy)
        http.auth.basic  credentials[:name], credentials[:pass] unless credentials.nil?
      end
      client
    end

    # Delivery reliability, particularly in the US, is appalling
    # and being able to reduce non-deliveries by more than half
    # is a big deal when sending transactional messages.
    def fallback_to_twilio
      sms.output = :twilio
      sms.deliver
    end

    def get_response_hash_from(response)
      response.to_hash[:submit_rsp]
    end

    def get_message_id_from hash
      hash[:message_id] rescue ''
    end

    def set_originator(originator)
      soap_body["originatingAddress"] = originator
      soap_body["originatorTON"] = (originator.length == 5 && originator.to_i.to_s == originator) ? "0" : "1"
    end

    def xmlns
      "http://www.ipx.com/api/services/smsapi52/types"
    end

    def wsdl_namespace
      "http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-6-MM7-1-2"
    end

    def parts
      @parts ||= split_parts
    end

    def split_parts
      Smess.split_sms(sms.message.strip_nongsm_chars).reject {|s| s.empty? }
    end

    # {050003}{ff}{02}{01} {concat-command}{id to link all parts}{total num parts}{num of current part}
    def concatenation_udh(num, total)
      "050003#{ref_id}#{total.to_s(16).rjust(2,'0')}#{(num).to_s(16).rjust(2,'0')}"
    end

    def ref_id
      @ref_id ||= Random.new.rand(255).to_s(16).rjust(2,"0")
    end

    def populate_soap_body(part, i)
      # if we have several parts, send them as concatenated sms using UDH codes
      soap_body["userDataHeader"] = concatenation_udh(i+1, parts.length) if parts.length > 1
      soap_body["userData"] = part
      soap_body["correlationId"] = Time.now.strftime('%Y%m%d%H%M%S') + sms.to + (i+1).to_s
    end

    def send_one_sms
      client = soap_client
      soap_body_var = soap_body
      begin
        response = client.request "SendRequest", "xmlns" => xmlns do
          soap.body = soap_body_var
        end
        result = parse_sms_response(response)
      rescue => e
        result = result_for_error(e)
        # LOG error here?
      end
      result
    end

    def last_result_was_error
      results.last.fetch(:response_code, '').to_s != "0"
    end

    def parse_sms_response(response)
      if response.http_error? || response.soap_fault?
        e = Struct.new(:code, :message).new("-1", response.http_error || response.soap_fault.to_hash)
        result = result_for_error(e)
      else
        result = normal_result(response)
      end
      result
    end

    def result_for_error(e)
      {
        response_code: '-1',
        response: {
          temporaryError: 'true',
          responseCode: '-1',
          responseText: e.message
        },
        data: result_data
      }
    end

    def normal_result(response)
      hash = response.to_hash[:send_response]
      message_id = ""
      message_id = hash[:message_id] if hash.has_key? :message_id
      response_code = hash[:response_code]

      {
        message_id: message_id,
        response_code: response_code,
        response: hash,
        destination_address: sms.to,
        data: result_data
      }
    end

    def result_data
      data = soap_body.dup
      data.delete "password"
      data["userData"] = sms.message.strip_nongsm_chars
      data
    end


    # Called before final message assembly
    # used to look up the operator and make changes to the SOAP data for some carriers
    def perform_operator_adaptation(msisdn)
    end



  end
end