<?php

namespace UpsFreeVendor\Ups;

use DateTime;
use Exception;
use UpsFreeVendor\Psr\Log\LoggerAwareInterface;
use UpsFreeVendor\Psr\Log\LoggerInterface;
use UpsFreeVendor\Psr\Log\NullLogger;
use SimpleXMLElement;
use SoapClient;
use UpsFreeVendor\Ups\Exception\InvalidResponseException;
class SoapRequest implements RequestInterface, LoggerAwareInterface
{
    /**
     * @var string
     */
    protected $access;
    /**
     * @var string
     */
    protected $request;
    /**
     * @var string
     */
    protected $endpointUrl;
    /**
     * @var LoggerInterface
     */
    protected $logger;
    /**
     * @param LoggerInterface $logger
     */
    public function __construct(LoggerInterface $logger = null)
    {
        if ($logger !== null) {
            $this->setLogger($logger);
        } else {
            $this->setLogger(new NullLogger());
        }
    }
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     *
     * @return null
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    /**
     * Send request to UPS.
     *
     * @param string $access The access request xml
     * @param string $request The request xml
     * @param string $endpointurl The UPS API Endpoint URL
     * @param string $operation Operation to perform on SOAP endpoint
     * @param string $wsdl Which WSDL file to use
     *
     * @throws Exception
     * @todo: make access, request and endpointurl nullable to make the testable
     *
     * @return ResponseInterface
     */
    public function request($access, $request, $endpointurl, $operation = null, $wsdl = null)
    {
        // Check for operation
        if (is_null($operation)) {
            throw new \Exception('Operation is required');
        }
        // Check for WSDL
        if (is_null($wsdl)) {
            throw new \Exception('WSDL is required');
        }
        // Set data
        $this->setAccess($access);
        $this->setRequest($request);
        $this->setEndpointUrl($endpointurl);
        // Settings based on UPS PHP Example
        $mode = array('soap_version' => 'SOAP_1_1', 'trace' => 1, 'connection_timeout' => 2, 'cache_wsdl' => \WSDL_CACHE_BOTH);
        // Initialize soap client
        $client = new SoapClient(__DIR__ . '/WSDL/' . $wsdl . '.wsdl', $mode);
        // Set endpoint URL + auth & request data
        $client->__setLocation($endpointurl);
        $auth = (array) new SimpleXMLElement($access);
        $request = (array) new SimpleXMLElement($request);
        // Build auth header
        $header = new \SoapHeader('http://www.ups.com/schema/xpci/1.0/auth', 'AccessRequest', $auth);
        $client->__setSoapHeaders($header);
        // Log request
        $date = new DateTime();
        $id = $date->format('YmdHisu');
        $this->logger->info('Request To UPS API', ['id' => $id, 'endpointurl' => $this->getEndpointUrl()]);
        $this->logger->debug('Request: ' . $this->getRequest(), ['id' => $id, 'endpointurl' => $this->getEndpointUrl()]);
        // Perform call and get response
        try {
            $request = json_decode(json_encode((array) $request), \true);
            $client->__soapCall($operation, [$request]);
            $body = $client->__getLastResponse();
            $this->logger->info('Response from UPS API', ['id' => $id, 'endpointurl' => $this->getEndpointUrl()]);
            $this->logger->debug('Response: ' . $body, ['id' => $id, 'endpointurl' => $this->getEndpointUrl()]);
            // Strip off namespaces and make XML
            $body = preg_replace('/(<\/*)[^>:]+:/', '$1', $body);
            $xml = new SimpleXMLElement($body);
            $responseInstance = new Response();
            return $responseInstance->setText($body)->setResponse($xml);
        } catch (\Exception $e) {
            // Parse error response
            $xml = new SimpleXMLElement($client->__getLastResponse());
            $xml->registerXPathNamespace('err', 'http://www.ups.com/schema/xpci/1.0/error');
            $errorCode = $xml->xpath('//err:PrimaryErrorCode/err:Code');
            $errorMsg = $xml->xpath('//err:PrimaryErrorCode/err:Description');
            if (isset($errorCode[0]) && isset($errorMsg[0])) {
                $this->logger->alert($errorMsg[0], ['id' => $id, 'endpointurl' => $this->getEndpointUrl()]);
                throw new InvalidResponseException('Failure: ' . (string) $errorMsg[0] . ' (' . (string) $errorCode[0] . ')');
            } else {
                $this->logger->alert($e->getMessage(), ['id' => $id, 'endpointurl' => $this->getEndpointUrl()]);
                throw new InvalidResponseException('Cannot parse error from UPS: ' . $e->getMessage(), $e->getCode(), $e);
            }
        }
    }
    /**
     * @return string
     */
    public function getEndpointUrl()
    {
        return $this->endpointUrl;
    }
    /**
     * @param $endpointUrl
     *
     * @return $this
     */
    public function setEndpointUrl($endpointUrl)
    {
        $this->endpointUrl = $endpointUrl;
        return $this;
    }
    /**
     * @return string
     */
    public function getRequest()
    {
        return $this->request;
    }
    /**
     * @param $request
     *
     * @return $this
     */
    public function setRequest($request)
    {
        $this->request = $request;
        return $this;
    }
    /**
     * @return string
     */
    public function getAccess()
    {
        return $this->access;
    }
    /**
     * @param $access
     *
     * @return $this
     */
    public function setAccess($access)
    {
        $this->access = $access;
        return $this;
    }
}
