<?php

class Tracking_FedEx {

    public static function getAuthToken() {
        $headers = [
            'Content-Type' => 'application/x-www-form-urlencoded'
        ];

        $postdata = http_build_query([
            "grant_type" => "client_credentials",
            "client_id" => env("fedex_apikey", ""),
            "client_secret" => env("fedex_secret", "")
        ]);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_TIMEOUT, 45);
        //curl_setopt($ch, CURLOPT_URL, "https://apis-sandbox.fedex.com/oauth/token"); // TEST
        curl_setopt($ch, CURLOPT_URL, "https://apis.fedex.com/oauth/token"); // PROD
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
        $response = curl_exec($ch);

        $data = json_decode($response, true);

        if (isset($data["access_token"])) {
            return [$data["access_token"], $data["expires_in"]];
        }
        throw new Exception("Couldn't get FedEx access token.");
    }

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

        $fedexauthtoken = $memcache->get("logistics.tracking.fedex_auth_token");
        if ($fedexauthtoken == false) {
            $tokendata = Tracking_FedEx::getAuthToken();
            $memcache->set("logistics.tracking.fedex_auth_token", $tokendata[0], $tokendata[1] - 120);
            $fedexauthtoken = $tokendata[0];
        }

        try {
            $trackrequest = [
                "includeDetailedScans" => true,
                "trackingInfo" => [
                    [
                        //"shipDateBegin" => date("Y-m-d", strtotime("now - 3 months")),
                        "trackingNumberInfo" => [
                            "trackingNumber" => $barcode->getCode()
                        ]
                    ]
                ]
            ];
            $headers = [
                "Authorization: Bearer $fedexauthtoken",
                'X-locale: en_US',
                'Content-Type: application/json'
            ];

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($ch, CURLOPT_TIMEOUT, 45);
            //curl_setopt($ch, CURLOPT_URL, "https://apis-sandbox.fedex.com/track/v1/trackingnumbers"); // TEST
            curl_setopt($ch, CURLOPT_URL, "https://apis.fedex.com/track/v1/trackingnumbers"); // PROD
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($trackrequest));
            curl_setopt($ch, CURLOPT_ENCODING, "");
            $response = curl_exec($ch);

            // CHECK TO SEE IF WE GOT AN ERROR
            if ((curl_errno($ch)) && (curl_errno($ch) != 0)) {
                throw new TrackingException(curl_error($ch));
            }

            $result = json_decode($response, true);

            if (isset($result["errors"]) || !empty($result["errors"])) {
                if (count($result["errors"]) > 0) {
                    throw new TrackingException($result["errors"][0]["message"]);
                }
                throw new TrackingException("The FedEx tracking system responded with an error. Try again later.");
            }

            if (!empty($result["output"]["completeTrackResults"]) && !empty($result["output"]["completeTrackResults"][0]["trackResults"])) {
                $trackinfo = $result["output"]["completeTrackResults"][0]["trackResults"][0];
            } else {
                throw new TrackingException("No tracking details found.");
            }
            if (!empty($result["output"]["completeTrackResults"][0]["trackResults"][0]["error"])) {
                throw new TrackingException($result["output"]["completeTrackResults"][0]["trackResults"][0]["error"]["message"]);
            }
        } catch (TrackingException $ex) {
            throw $ex;
        } catch (Exception $ex) {
            throw new TrackingException("There was a server problem. This code cannot be tracked right now.");
        }

        $info = new TrackingInfo();

        $info->setCode($trackinfo["trackingNumberInfo"]["trackingNumber"]);

        $info->setCarrier("fedex");
        $info->setService(new Service($trackinfo["serviceDetail"]["type"], $trackinfo["serviceDetail"]["description"]));

        // Set carrier info: Ground, Express, Freight, etc
        $carrierid = "fedex";
        switch ($trackinfo["trackingNumberInfo"]["carrierCode"]) {
            case "FEDEX_GROUND":
            case "FEDEX_EXPRESS":
            case "FEDEX_FREIGHT":
            default:
                $carrierid = "fedex";
                break;
        }
        $info->setCarrierAttributionText(CarrierAssets::getAttribution(Carriers::getCarrierCode($carrierid)));
        $info->setCarrierLogo(CarrierAssets::getLogo(Carriers::getCarrierCode($carrierid)));

        if (count($trackinfo["scanEvents"]) > 0) {
            $current = $trackinfo["scanEvents"][0];
            $current_status = new TrackingEntry(
                    TrackingStatus::FedExEventTypeToStatus($current["eventType"]),
                    ($current["eventDescription"] ?? "Unknown")
                    . (TrackingStatus::FedExEventTypeToStatus($current["eventType"]) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . $current["eventType"] : ""),
                    date("c", strtotime($current["date"]))
            );

            $current_location = new Location();
            // FedEx docs say it should be $current["scanLocation"]["locationContactAndAddress"]["address"]["...}] but it's not I guess
            $current_location->city = $current["scanLocation"]["city"] ?? "";
            $current_location->state = $current["scanLocation"]["stateOrProvinceCode"] ?? "";
            $current_location->zip = $current["scanLocation"]["postalCode"] ?? "";
            $current_location->country = $current["scanLocation"]["countryCode"] ?? "";
            $current_status->setLocation($current_location);

            $info->setCurrentStatus($current_status);
        }


        $to = new Location();
        $to->city = $trackinfo["recipientInformation"]["address"]["city"] ?? "";
        $to->state = $trackinfo["recipientInformation"]["address"]["stateOrProvinceCode"] ?? "";
        $to->zip = $trackinfo["recipientInformation"]["address"]["postalCode"] ?? "";
        $to->country = $trackinfo["recipientInformation"]["address"]["countryCode"] ?? "";
        $info->setTo($to);

        $from = new Location();
        $from->city = $trackinfo["shipperInformation"]["address"]["city"] ?? "";
        $from->state = $trackinfo["shipperInformation"]["address"]["stateOrProvinceCode"] ?? "";
        $from->zip = $trackinfo["shipperInformation"]["address"]["postalCode"] ?? "";
        $from->country = $trackinfo["shipperInformation"]["address"]["countryCode"] ?? "";
        $info->setFrom($from);

        foreach ($trackinfo["scanEvents"] as $history) {
            $location = new Location();
            $location->city = $history["scanLocation"]["city"] ?? "";
            $location->state = $history["scanLocation"]["stateOrProvinceCode"] ?? "";
            $location->zip = $history["scanLocation"]["postalCode"] ?? "";
            $location->country = $history["scanLocation"]["countryCode"] ?? "";
            if (!empty($history["date"])) {
                $datetimestring = date("c", strtotime($history["date"]));
            } else {
                $datetimestring = "";
            }
            $info->appendHistoryEntry(new TrackingEntry(
                            TrackingStatus::FedExEventTypeToStatus($history["eventType"]),
                            ($history["eventDescription"] ?? "Unknown")
                            . (TrackingStatus::FedExEventTypeToStatus($history["eventType"]) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . $history["eventType"] : ""),
                            $datetimestring,
                            $location));
        }

        return $info;
    }

}
