<?php

class Tracking_USPS {

    /**
     * Sometimes the API returns a city but no state or ZIP. This is a lookup table of city => state
     * so the API can return a more complete response.
     */
    public const STATELESS_CITIES = [
        "LOS ANGELES" => "CA",
        "SAN FRANCISCO" => "CA",
        "NEW YORK" => "NY",
        "CHICAGO" => "IL",
        "MIAMI" => "FL"
    ];

    /**
     *
     * @param string $code
     * @return \TrackingInfo
     * @throws TrackingException
     */
    public static function track(string $code, string $carrier = ""): TrackingInfo {
        $barcode = new TrackingBarcode($code);

        try {

            $resp = USPSAPIs::getAPIRequest("tracking/v3/tracking/$code?expand=DETAIL");
            $resp = str_replace("<SUP>reg;</SUP>", "®", $resp);
            $resp = str_replace("<SUP>&reg;</SUP>", "®", $resp);
            $resp = str_replace("<SUP>&#153;</SUP>", "™", $resp);
            $json = json_decode($resp, true);

            if (!empty($json["error"])) {
                if (!empty($json["error"]["errors"])) {
                    $msg = $json["error"]["errors"][0]["title"];
                    $msg = preg_replace("/\s?-\s?$/", "", $msg); // Remove weird dash at end of message
                    switch ($json["error"]["errors"][0]["code"]) {
                        case "150001":
                            // Tracking number not found
                            throw new TrackingException(str_replace("12: ", "", $msg));
                        case "150002":
                            // Incorrect tracking number
                            throw new TrackingException(str_replace("7: ", "", $msg));
                    }
                } else if (!empty($json["error"]["errors"]) && !empty($json["error"]["errors"][0]["code"])) {
                    throw new TrackingException("The USPS tracking system is having problems: \"" . trim($json["error"]["message"]) . "\" (" . $json["error"]["errors"][0]["code"] . ")");
                } else if (!empty($json["error"]["message"])) {
                    throw new TrackingException("The USPS tracking system is having problems: \"" . trim($json["error"]["message"]) . "\"");
                }
                throw new TrackingException("The USPS tracking system is having problems. Try again later.");
            }

            $trackinfo = $json;
        } catch (TrackingException $ex) {
            throw $ex;
        } catch (Exception $ex) {
            throw new TrackingException("There was a server error. This code cannot be tracked right now. Try again later.");
        }

        $info = new TrackingInfo();

        try {
            $info->setCode($trackinfo["trackingNumber"]);
        } catch (Exception $ex) {
            throw new TrackingException("The USPS tracking system returned an invalid response. Try again later.");
        }

        $info->setCarrier("usps");
        $info->setService(new Service((string) $trackinfo["mailClass"], (string) $trackinfo["mailClass"]));
        $info->setCarrierAttributionText(CarrierAssets::getAttribution(Carriers::getCarrierCode($info->getCarrier())));
        $info->setCarrierLogo(CarrierAssets::getLogo(Carriers::getCarrierCode($info->getCarrier())));

        // Current status
        if (count($trackinfo["trackingEvents"]) > 0) {
            $index = 0;
            $evt = $trackinfo["trackingEvents"][$index];
            $current_status = new TrackingEntry(
                    TrackingStatus::USPSEventCodeToStatus($evt["eventCode"]),
                    ($evt["eventType"] ?? "Unknown") . (TrackingStatus::USPSEventCodeToStatus($evt["eventCode"]) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . $evt["eventCode"] : ""),
                    date("Y-m-d\TH:i:s", strtotime($evt["eventTimestamp"])),
                    null,
                    TrackingStatus::isUSPSEventCodeContainerScan($evt["eventCode"])
            );

            $current_location = new Location();
            $current_location->city = (string) $evt["eventCity"] ?? "";
            $current_location->state = (string) $evt["eventState"] ?? "";
            $current_location->zip = (string) $evt["eventZIP"] ?? "";
            $current_location->country = (string) $evt["eventCountry"] ?? "";

            /*
             * Fill in state from list above when it's missing from the API response
             */
            if ($current_location->state == "" && $current_location->zip == "") {
                if (array_key_exists(strtoupper($current_location->city), self::STATELESS_CITIES)) {
                    $current_location->state = self::STATELESS_CITIES[$current_location->city];
                }
            }
        } else {
            $current_status = new TrackingEntry(
                    TrackingStatus::TRACKING_STATUS_PRE_TRANSIT,
                    ($trackinfo["statusSummary"] ?? "Unknown"),
                    date("Y-m-d\TH:i:s", strtotime("now")),
                    null,
                    false
            );
            $current_location = new Location();
        }

        $current_status->setLocation($current_location);

        $info->setCurrentStatus($current_status);

        $from = new Location();
        $from->city = (string) (isset($trackinfo["originCity"]) ? $trackinfo["originCity"] : "");
        $from->state = (string) (isset($trackinfo["originState"]) ? $trackinfo["originState"] : "");
        $from->zip = (string) (isset($trackinfo["originZIP"]) ? $trackinfo["originZIP"] : "");
        $from->country = (string) (isset($trackinfo["originCountry"]) ? $trackinfo["originCountry"] : "");

        $info->setFrom($from);

        $to = new Location();
        $to->city = (string) (isset($trackinfo["destinationCity"]) ? ($trackinfo["destinationCity"] ?? "") : "");
        $to->state = (string) (isset($trackinfo["destinationState"]) ? ($trackinfo["destinationState"] ?? "") : "");
        $to->zip = (string) (isset($trackinfo["destinationZIP"]) ? ($trackinfo["destinationZIP"] ?? "") : "");
        $to->country = (string) (isset($trackinfo["destinationCountry"]) ? $trackinfo["destinationCountry"] : "");

        $info->setTo($to);

        for ($i = 0; $i < count($trackinfo["trackingEvents"]); $i++) {
            $history = $trackinfo["trackingEvents"][$i];
            $location = new Location();
            $location->city = (string) $history["eventCity"] ?? "";
            $location->state = (string) $history["eventState"] ?? "";
            $location->zip = (string) $history["eventZIP"] ?? "";
            $location->country = (string) $history["eventCountry"] ?? "";
            /*
             * Fill in state from list above when it's missing from the API response
             */
            if ($location->state == "" && $location->zip == "") {
                if (array_key_exists(strtoupper($location->city), self::STATELESS_CITIES)) {
                    $location->state = self::STATELESS_CITIES[$location->city];
                }
            }
            $datetime = $history["eventTimestamp"];
            if (empty($history["eventTimestamp"])) {
                if ($i > 0 && !empty($trackinfo["trackingEvents"][$i - 1]["eventTimestamp"])) {
                    $datetime = $trackinfo["trackingEvents"][$i - 1]["eventTimestamp"];
                } else if ($i < count($trackinfo["trackingEvents"]) - 1 && !empty($trackinfo["trackingEvents"][$i + 1]["eventTimestamp"])) {
                    $datetime = $trackinfo["trackingEvents"][$i + 1]["eventTimestamp"];
                } else {
                    $datetime = "1970-01-01";
                }
            }
            $info->appendHistoryEntry(new TrackingEntry(
                            TrackingStatus::USPSEventCodeToStatus((string) $history["eventCode"]),
                            $history["eventType"] . (TrackingStatus::USPSEventCodeToStatus((string) $history["eventCode"]) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . (string) $history["eventCode"] : ""),
                            $datetime,
                            $location,
                            TrackingStatus::isUSPSEventCodeContainerScan((string) $history["eventCode"])));
        }

        return $info;
    }
}
