<?php

namespace UpsFreeVendor\Ups;

use DOMDocument;
use Exception;
use UpsFreeVendor\Psr\Log\LoggerInterface;
use SimpleXMLElement;
use stdClass;
use DateTime;
/**
 * Tracking API Wrapper.
 */
class Tracking extends Ups
{
    const ENDPOINT = '/Track';
    /**
     * @var RequestInterface
     */
    private $request;
    /**
     *
     * Workaround flag to handle Multiple shipment nodes in tracking response
     * See GitHub Issue #117
     *
     * @todo: fix in next major release
     *
     * @var boolean
     */
    protected $allowMultipleShipments = \false;
    /**
     * @todo: make private
     *
     * @var ResponseInterface
     */
    public $response;
    /**
     * @var string
     */
    private $trackingNumber;
    /**
     * @var string
     */
    private $referenceNumber;
    /**
     * @var string
     */
    private $requestOption;
    /**
     * @var string
     */
    private $shipperNumber;
    /**
     * @var \DateTime
     */
    private $beginDate;
    /**
     * @var \DateTime
     */
    private $endDate;
    /**
     * @param string|null $accessKey UPS License Access Key
     * @param string|null $userId UPS User ID
     * @param string|null $password UPS User Password
     * @param bool $useIntegration Determine if we should use production or CIE URLs.
     * @param RequestInterface|null $request
     * @param LoggerInterface|null $logger PSR3 compatible logger (optional)
     */
    public function __construct($accessKey = null, $userId = null, $password = null, $useIntegration = \false, RequestInterface $request = null, LoggerInterface $logger = null)
    {
        if (null !== $request) {
            $this->setRequest($request);
        }
        parent::__construct($accessKey, $userId, $password, $useIntegration, $logger);
    }
    /**
     * Get package tracking information.
     *
     * @param string $trackingNumber The package's tracking number.
     * @param string $requestOption Optional processing. For Mail Innovations the only valid options are Last Activity and All activity.
     *
     * @throws Exception
     *
     * @return stdClass
     */
    public function track($trackingNumber, $requestOption = 'activity')
    {
        $this->trackingNumber = $trackingNumber;
        $this->requestOption = $requestOption;
        return $this->getFormattedResponse();
    }
    /**
     * Get package tracking information.
     *
     * @param string $referenceNumber Reference numbers can be a purchase order number, job number, etc. Reference number can be added when creating a shipment.
     * @param string $requestOption
     *
     * @throws Exception
     *
     * @return stdClass
     */
    public function trackByReference($referenceNumber, $requestOption = 'activity')
    {
        $this->referenceNumber = $referenceNumber;
        $this->requestOption = $requestOption;
        return $this->getFormattedResponse();
    }
    /**
     * Set shipper number
     *
     * @param string $shipperNumber
     *
     */
    public function setShipperNumber($shipperNumber)
    {
        $this->shipperNumber = $shipperNumber;
    }
    /**
     * Set begin date
     *
     * @param DateTime $beginDate
     *
     */
    public function setBeginDate(DateTime $beginDate)
    {
        $this->beginDate = $beginDate;
    }
    /**
     * Set end date
     *
     * @param DateTime $endDate
     *
     */
    public function setEndDate(DateTime $endDate)
    {
        $this->endDate = $endDate;
    }
    /**
     * @return stdClass
     * @throws Exception
     */
    private function getFormattedResponse()
    {
        $this->response = $this->getRequest()->request($this->createAccess(), $this->createRequest(), $this->compileEndpointUrl(self::ENDPOINT));
        $response = $this->response->getResponse();
        if (null === $response) {
            throw new Exception('Failure (0): Unknown error', 0);
        }
        if ($response instanceof SimpleXMLElement && $response->Response->ResponseStatusCode == 0) {
            throw new Exception("Failure ({$response->Response->Error->ErrorSeverity}): {$response->Response->Error->ErrorDescription}", (int) $response->Response->Error->ErrorCode);
        }
        return $this->formatResponse($response);
    }
    /**
     * Check if tracking number is for mail innovations.
     *
     * @return bool
     */
    private function isMailInnovations()
    {
        $patterns = [
            // UPS Mail Innovations tracking numbers
            '/^MI\d{6}\d{1,22}$/',
            // MI 000000 00000000+
            // USPS - Certified Mail
            '/^94071\d{17}$/',
            // 9407 1000 0000 0000 0000 00
            '/^7\d{19}$/',
            // 7000 0000 0000 0000 0000
            // USPS - Collect on Delivery
            '/^93033\d{17}$/',
            // 9303 3000 0000 0000 0000 00
            '/^M\d{9}$/',
            // M000 0000 00
            // USPS - Global Express Guaranteed
            '/^82\d{10}$/',
            // 82 000 000 00
            // USPS - Priority Mail Express International
            '/^EC\d{9}US$/',
            // EC 000 000 000 US
            // USPS Innovations Expedited
            '/^927\d{23}$/',
            // 9270 8900 8900 8900 8900 8900 00
            // USPS - Priority Mail Express
            '/^927\d{19}$/',
            // 9270 1000 0000 0000 0000 00
            '/^EA\d{9}US$/',
            // EA 000 000 000 US
            // USPS - Priority Mail International
            '/^CP\d{9}US$/',
            // CP 000 000 000 US
            // USPS - Priority Mail
            '/^92055\d{17}$/',
            // 9205 5000 0000 0000 0000 00
            '/^14\d{18}$/',
            // 1400 0000 0000 0000 0000
            // USPS - Registered Mail
            '/^92088\d{17}$/',
            // 9208 8000 0000 0000 0000 00
            '/^RA\d{9}US$/',
            // RA 000 000 000 US
            // USPS - Signature Confirmation
            '/^9202\d{16}US$/',
            // 9202 1000 0000 0000 0000 00
            '/^23\d{16}US$/',
            // 2300 0000 0000 0000 0000
            // USPS - Tracking
            '/^94\d{20}$/',
            // 9400 1000 0000 0000 0000 00
            '/^03\d{18}$/',
        ];
        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $this->trackingNumber)) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * Create the Tracking request.
     *
     * @return string
     */
    private function createRequest()
    {
        $xml = new DOMDocument();
        $xml->formatOutput = \true;
        $trackRequest = $xml->appendChild($xml->createElement('TrackRequest'));
        $trackRequest->setAttribute('xml:lang', 'en-US');
        $request = $trackRequest->appendChild($xml->createElement('Request'));
        $node = $xml->importNode($this->createTransactionNode(), \true);
        $request->appendChild($node);
        $request->appendChild($xml->createElement('RequestAction', 'Track'));
        if (null !== $this->requestOption) {
            $request->appendChild($xml->createElement('RequestOption', $this->requestOption));
        }
        if (null !== $this->trackingNumber) {
            $trackRequest->appendChild($xml->createElement('TrackingNumber', $this->trackingNumber));
        }
        if ($this->isMailInnovations()) {
            $trackRequest->appendChild($xml->createElement('IncludeMailInnovationIndicator'));
        }
        if (null !== $this->referenceNumber) {
            $trackRequest->appendChild($xml->createElement('ReferenceNumber'))->appendChild($xml->createElement('Value', $this->referenceNumber));
        }
        if (null !== $this->shipperNumber) {
            $trackRequest->appendChild($xml->createElement('ShipperNumber', $this->shipperNumber));
        }
        if (null !== $this->beginDate || null !== $this->endDate) {
            $DateRange = $xml->createElement('PickupDateRange');
            if (null !== $this->beginDate) {
                $beginDate = $this->beginDate->format('Ymd');
                $DateRange->appendChild($xml->createElement('BeginDate', $beginDate));
            }
            if (null !== $this->endDate) {
                $endDate = $this->endDate->format('Ymd');
                $DateRange->appendChild($xml->createElement('EndDate', $endDate));
            }
            $trackRequest->appendChild($DateRange);
        }
        return $xml->saveXML();
    }
    /**
     * Format the response.
     *
     * @param SimpleXMLElement $response
     *
     * @return stdClass
     */
    private function formatResponse(SimpleXMLElement $response)
    {
        if ($this->allowMultipleShipments) {
            $response = $this->convertXmlObject($response);
            if (!is_array($response->Shipment)) {
                $response->Shipment = [$response->Shipment];
            }
            return $response;
        }
        return $this->convertXmlObject($response->Shipment);
    }
    /**
     * @return RequestInterface
     */
    public function getRequest()
    {
        if (null === $this->request) {
            $this->request = new Request($this->logger);
        }
        return $this->request;
    }
    /**
     * @param RequestInterface $request
     *
     * @return $this
     */
    public function setRequest(RequestInterface $request)
    {
        $this->request = $request;
        return $this;
    }
    /**
     * @return ResponseInterface
     */
    public function getResponse()
    {
        return $this->response;
    }
    /**
     * @param ResponseInterface $response
     *
     * @return $this
     */
    public function setResponse(ResponseInterface $response)
    {
        $this->response = $response;
        return $this;
    }
    /**
     * @param bool $value
     * @return $this
     */
    public function allowMultipleShipments($value = \true)
    {
        $this->allowMultipleShipments = $value ? \true : \false;
        return $this;
    }
}
