<?php

class QuoteApi extends Api
{
    public $apiName = 'quote';
    const CACHE_TIMEOUT = 300; // 5 minutes

		const UBER_DISTANCE_RESTRICTION = 320;
		const LYFT_DISTANCE_RESTRICTION = 499;
		const SINGAPORE_DISTANCE_RESTRICTION = 48;
		const AUTOCAB_DISTANCE_RESTRICTION = 320; // TODO: Check and update late


    public function __construct($requestUri, $encryption=true) {
//      $this->cacheWhitelist = [
//        "viewAction" => ['ttl' => 300], // 300 sec. = 5 min.
//        "createAction" => ['ttl' => 300] // 300 sec. = 5 min.
//      ];
      parent::__construct($requestUri, $encryption);
    }
    public function indexAction() {
        $origin = $this->requestParams["origin"] ?? "";
        $destination = $this->requestParams["destination"] ?? "";
        $transport_provider_id = $this->requestParams["transport_provider_id"] ?? "";
        $limit = 10;
        $offset = 0;
        if ($destination && $origin) {
            $db =new Db();
            list($total, $quotes) = Quotes::getByOriginDestinationId(
                $db->getConnect(), 
                $origin, $destination, $transport_provider_id,
                $limit, $offset
            );
            if(is_array($quotes) && count($quotes)>0) {
                return $this->response(array(
                    'origin' => $origin,
                    'destination' => $destination,
                    'count' => count($quotes),
                    'limit' => $limit,
                    'offset' => $offset,
                    'total' => $total,
                    'quotes' => $quotes
                ), 200);
            }
        }
        return $this->response(
            array(
                'error' => 'Data not found'
            ), 404);
    }

    /**
     * Method GET
     * Get single record (by id)
     * http://DOMAIN/quote/1
     * @return string
     */
    public function viewAction() {
        //id must be the first parameter after /trips/x
        $id = array_shift($this->requestUri);
        $message = 'Data not found';
        $code = 404;
        try {
            if($id && (int)$id>0) {
                $db = new Db();
                $quote = QuoteApi::viewReal($db, $id);
                if (is_array($quote)) {
                    return $this->response($quote, 200);
                }
            }
        } catch (Exception $e) {
            $code = 500;
            $messge = $e->getMessage();
        }
        return $this->response(
            array(
                'error'=> $message
            ), $code);
    }

    public static function viewReal($db, $id) {
        $quote = Quotes::getById($db->getConnect(), (int)$id);
        if(is_array($quote) && count($quote)>0) {
            $log = [
                'message' => 'viewReal quote',
                'data' => $quote
            ];
            Logger::debug($log);
            $quote["deeplink"] = Quotes::deeplink($db->getConnect(), $quote);
            // Check "completed"
            if ($quote["completed"]!="" && $quote['travel_date']!="" && (int)$quote["cost"]!=0) {
                /* $log = [
                    'message' => 'completed quote',
                    'data' => $quote
                ];
                Logger::debug($log); */
                return $quote; // OK!
            }
            // Call service if needed
            if ($quote["automation_id"]>0) {
                list($quoteService, $err) = QuoteAPI::checkQuote($quote["automation_id"]);
                // Update
                if (is_array($quoteService) && $quoteService["complete"]!="") {
                    $quoteService["automation_id"] = $quoteService["id"];
                    $quoteService["id"] = (int)$id;
                    $quoteService["completed"] = "now()";
                    $timezone = Address::getTzByAddressId($db->getConnect(),$quote["location_start_id"]);
                    $quoteService["travel_date"] = Utilities::convertUtcToLocal($quoteService["complete"],$timezone);
                    $quoteService["created"] = $quote["created"];
                    $quoteService["location_start_id"] = $quote["location_start_id"];
                    $quoteService["location_end_id"] = $quote["location_end_id"];
                    $quoteService["member_id"] = $quote["member_id"];
                    $quoteService["deeplink"] = $quote["deeplink"];
                    $log = [
                        'message' => 'QuoteApi::viewReal update',
                        'data' =>$quoteService
                    ];
                    Logger::debug($log);
                    list ($res, $err) = Quotes::update($db->getConnect(), $quoteService);
                    /* $log = [
                        'message' => 'QuoteApi::viewReal update response',
                        'data' =>$res,
                        'error' =>$err
                    ];
                    Logger::debug($log); */
                    if ($quoteService["cost"]==0 && 
                        $quoteService["message"]=="android_automation_job_detail") {
                        // We must take the details
                        $quoteService = QuoteApi::quoteDetails($db, $quoteService);
                        return $quoteService;
                    }
                    return $quoteService;
                } 
            }
            /* $log = [
                'message' => 'can not checkquote from android_automation',
                'data' =>$quote
            ];
            Logger::debug($log); */
            return $quote;
        }
        return null;
    }

    public function quoteDetails($db, $quoteService) {
        list($details,$err) = self::getQuoteDetails($quoteService["automation_id"]);
        if ($details==NULL) {
            error_log($err);
            $quoteService['error'] = $err;
        }
        //$quoteService['details'] = $details;
        $low_estimate = $quoteService['cost'];
        foreach ($details as $item) {
            $c_low_estimate = sprintf("%0.02f", 0.01*$item["low_estimate"]);
            // We show GrabShare only for now!
            if ($c_low_estimate==0 || $item["name"]!='JustGrab') continue;
            if ($low_estimate==0 || $low_estimate>$c_low_estimate) {
                error_log("quoteDetails: ".$item["name"]." => ".$c_low_estimate);
                $low_estimate = $c_low_estimate;
            }
        }
        $quoteService['cost'] = $low_estimate;
        // Update cost
        $log = [
            'message' => 'get automation detail and update quotes',
            'data' =>$quoteService
        ];
        Logger::debug($log); 
        list ($res, $err) = Quotes::update($db->getConnect(), $quoteService);
        /* $log = [
            'message' => 'QuoteApi::getQuoteDetails update response',
            'data' =>$res,
            'error' =>$err
        ];
        Logger::debug($log); */
        return $quoteService;
    }

    public static function createReal($db, $origin, $destination, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country=GeocodeApi::DEFAULT_COUNTRY_CODE, $prefill='f',$pool=1) {

        $originStr = is_array($origin) ? json_encode($origin) : $origin;
        $destinationStr = is_array($destination) ? json_encode($destination) : $destination;
        syslog(LOG_WARNING,"QuoteApi::createReal(\$db, $originStr, $destinationStr, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country, $prefill, $pool");
        $message = "Failed to schedule quote";
        $code = 500;
        $action = "";
        $data = [];
        try {
            if (!in_array($transport_provider_id, [1,2,3,4,5,8])) {
                throw new Exception('Unsupported transport provider ID');
            }
            if (!$destination || !$origin) {
                throw new Exception('Invalid origin and/or destination address');
            }

            $originData= NULL;
            $destinationData = NULL;

            if (is_array($origin)) {
              $address = $origin["address"];
              if (0 === count(array_diff(['lat', 'lng', 'address', 'postal', 'timeZoneId', 'country'], array_keys($origin)))) {
                $originData = $origin;
              }
              $origin = $address;
            }
            if (is_array($destination)) {
              $address = $destination["address"];
              if (0 === count(array_diff(['lat', 'lng', 'address', 'postal', 'timeZoneId', 'country'], array_keys($destination)))) {
                $destinationData = $destination;
              }
              $destination = $address;
            }

            // Check if exists
            if (isset($originData) && isset($destinationData)) {
              $quoteUpdate = Quotes::getLastQuoteByOriginDestinationCoordinates($db->getConnect(),
                $originData, $destinationData, $transport_provider_id,SELF::CACHE_TIMEOUT);
            } else {
              $quoteUpdate = Quotes::getLastQuoteByOriginDestinationId($db->getConnect(),
                $origin, $destination, $transport_provider_id,SELF::CACHE_TIMEOUT);
            }

            /* $log = [
              'message' => 'QuoteApi::createReal getLastQuoteByOriginDestinationId',
              'quote' => $quoteUpdate
            ];
            Logger::debug($log); */

            if(isset($quoteUpdate)) {
              $id = $quoteUpdate["id"];
              $completed = $quoteUpdate["completed"];
              if(empty($quoteUpdate['travel_date']) && $completed != "") {
                if (isset($originData)) {
                  $timezone = $originData['timeZoneId'];
                } else {
                  $timezone = Address::getTzByAddressId($db->getConnect(),$quoteUpdate["location_start_id"]);
                }
                $quoteUpdate["travel_date"] = Utilities::convertUtcToLocal($completed,$timezone);

                list ($res, $err) = Quotes::update($db->getConnect(), $quoteUpdate);
                /* $log = [
                  'message' => 'QuoteApi::createReal getQuoteFromCache update response',
                  'data' => $res,
                  'errror' => $err
                ];
                Logger::debug($log); */
              }
              return ["Quote from cache",200,"view",["id"=>$id]];
            }

            // Geocode origin and destination
            $headers = getallheaders();
            $client_id = isset($headers['client_id'])?$headers['client_id']:"Unknown";
            if (!isset($originData)) {
              list($originData, $err) = GeocodeAPI::geocode($db, $origin, $country,$client_id);
              
              /* $log = [
                'message' => 'QuoteApi::geocode origin',
                'data' => ['client_id'=>$client_id, 'address_input'=>$origin],
                'errror' => $err
              ];
              Logger::debug($log); */
              if ($err || !isset($originData["lat"])) throw new Exception("Origin geocoding failed: ".$err);
            }

            if (!isset($destinationData)) {
              list($destinationData, $err) = GeocodeAPI::geocode($db, $destination, $country,$client_id);
              /* $log = [
                'message' => 'QuoteApi::geocode destination',
                'data' => ['client_id'=>$client_id, 'address_input'=>$destination],
                'errror' => $err
              ];
              Logger::debug($log); */
              if ($err || !isset($destinationData["lat"])) throw new Exception("Destination geocoding failed: ".$err);
            }


            $availableTransportProviders = Gis::getCityServicesAvailableForCoordinates($db->getConnect(), $originData["lat"], $originData["lng"],[$transport_provider_id]);
            if (count($availableTransportProviders) == 0) {
	            $availableTransportProviders = Gis::getCountryServicesAvailableForCoordinates( $db->getConnect(), $originData["lat"], $originData["lng"],[$transport_provider_id]);
            }

            if (count($availableTransportProviders) == 0) {
	            $message = "No transport providers available for origin coordinates (".$originData["lat"].",".$originData["lng"].").";
	            throw new Exception($message);
            }

            $distance = Gis::cosinesDistanceBetweenTwoGpsCoordinates($originData["lat"], $originData["lng"], $destinationData["lat"], $destinationData["lng"], "K");

            $isDistanceRestricted = false;
            switch ($transport_provider_id) {
	            case Quotes::VENDOR_UBER:
		            $isDistanceRestricted = $distance > QuoteApi::UBER_DISTANCE_RESTRICTION;
	            	break;
	            case Quotes::VENDOR_LYFT:
		            $isDistanceRestricted = $distance > QuoteApi::LYFT_DISTANCE_RESTRICTION;
	            	break;
	            case Quotes::VENDOR_GRAB: case Quotes::VENDOR_GOJEK: case Quotes::VENDOR_COMFORTDELGRO:
		            $isDistanceRestricted = $distance > QuoteApi::SINGAPORE_DISTANCE_RESTRICTION;
	            	break;
	            case Quotes::VENDOR_AUTOCAB:
		            $isDistanceRestricted = $distance > QuoteApi::AUTOCAB_DISTANCE_RESTRICTION;
		            break;
            }

            if ($isDistanceRestricted) {
            	$message = "Distance restriction. Could not get quote for (".$originData["lat"].",".$originData["lng"].") and (".$destinationData["lat"].",".$destinationData["lng"]."). The distance (".$distance." km) too long.";
            	throw new Exception($message);
            }

            $isOriginWithin = false;
            $isDestinationWithin = false;
            // Check if we are within Singapore: for Grab, Gojek, CDG
            if ($transport_provider_id>2 && $transport_provider_id<6) {
                $city   = 'Singapore';
                $sgLat  = 1.352083;
                $sgLng  = 103.819836;
                $radius = 25; // km
                $isOriginWithin = GeocodeAPI::withinCity(
                    $originData["address"],$originData["lat"],$originData["lng"],
                    $city,$sgLat,$sgLng,$radius);
                $isDestinationWithin = GeocodeAPI::withinCity(
                    $destinationData["address"],$destinationData["lat"],$destinationData["lng"],
                    $city,$sgLat,$sgLng,$radius);
            }
            // Uber, Lyft and Autocab: no check required
	          if (in_array($transport_provider_id, [1,2,8])) {
                $isOriginWithin = true;
                $isDestinationWithin = true;
            }
            
            if ($isOriginWithin && $isDestinationWithin) {
                syslog(LOG_WARNING,"'".$originData["address"]."' and '".$destinationData["address"]."' are within Singapore");
            } else {
                throw new Exception("Origin and/or destination is not within service area!");
            }
            // Schedule the quote
            $requestData = array(
                "location_start" => $originData["address"] ?? $origin,
                "location_end" => $destinationData["address"] ?? $destination,
                "trackedemail_item_id" => $trackedemail_item_id,
                "transport_provider_id" => $transport_provider_id,
                "location_start_lat" => $originData["lat"],
                "location_start_lng" => $originData["lng"],
                "location_start_country" => $originData["country"],
                "location_end_lat" => $destinationData["lat"],
                "location_end_lng" => $destinationData["lng"],
                "location_end_country" => $destinationData["country"],
                "pool" => $pool
            );
            list($res, $err) = QuoteAPI::scheduleQuote($requestData);
            if ($res==NULL || !isset($res["id"])) {
                throw new Exception("Quote schedule failed: " . ($err ?? $message));
            } 
            // Save to DB
            $requestData["location_start_tz"] = $originData["timeZoneId"];
            $requestData["location_start_postal"] = $originData["postal"];
            $requestData["location_start_country"] = $originData["country"];
            $requestData["location_end_tz"] = $destinationData["timeZoneId"];
            $requestData["location_end_postal"] = $destinationData["postal"];
            $requestData["location_end_country"] = $destinationData["country"];
            $requestData["automation_id"] = $res["id"];
            $requestData["member_id"] = (int)$member_id;
            $requestData["quote_group_id"] = (int)$quote_group_id;
            $requestData["prefill"] = $prefill;
            $requestData["pool"] = $pool;
            error_log('quote_group_id='.((int)$quote_group_id));
            unset($requestData["trackedemail_item_id"]);
            $log = [
                'message' => 'createReal quote',
                'data' =>$requestData
            ];
            Logger::debug($log); 
            list($quote,$err) = Quotes::create($db->getConnect(), $requestData);    
            /* $log = [
                'message' => 'createReal Quotes::create response',
                'quote' => $quote
            ];        
            Logger::debug($log); */ 
            if ($quote!=NULL && is_array($quote)) {
                $quote["deeplink"] = Quotes::deeplink($db->getConnect(), $quote, $requestData);
                return ["Quote scheduled",200,"response",$quote];
            } else {
                $message = "Failed to create quote record: ".$err;
            }
        } catch (Exception $e) {
            syslog(LOG_WARNING,json_encode($e));
            $message = $e->getMessage();
        }
        syslog(LOG_WARNING,$message);
        return [$message,$code,$action,$data];
    }

    public function createAction() {
        syslog(LOG_WARNING,"QuoteApi::createAction()");
        $origin = $this->requestParams["origin"] ?? "";
        $destination = $this->requestParams["destination"] ?? "";
        $member_id = $this->requestParams["member_id"] ?? 0;
        $country = $this->requestParams["country"] ?? GeocodeApi::DEFAULT_COUNTRY_CODE;
        $transport_provider_id = $this->requestParams["transport_provider_id"] ?? 0;
        $group_quote_id = $this->requestParams["group_quote_id"] ?? 0;
        $trackedemail_item_id = $this->requestParams["trackedemail_item_id"] ?? 0;
        $prefill = $this->requestParams["prefill"] ?? 'f';
        $pool = $this->requestParams["pool"] ?? 1;

        if ($prefill!='t') $prefill = 'f';
//        if ($pool!=2) $pool = 1; // 1 - main pool, 2 - dedicated pool

        $db = new Db();

        // DEBUG
        $country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default');

        list($message,$code,$action,$data) = QuoteApi::createReal(
            $db, $origin, $destination, $member_id, $transport_provider_id, 
            $group_quote_id, $trackedemail_item_id, $country, $prefill, $pool
        );
        syslog(LOG_WARNING,"message=${message}");
        syslog(LOG_WARNING,"code=${code}");
        syslog(LOG_WARNING,"action=${action}");
        syslog(LOG_WARNING,"data=".json_encode($data));
        if ($code==200) {
            if ($action=="view") {
                $id = $data["id"];
                array_unshift($this->requestUri, $id);
                return $this->viewAction();
            } else if ($action=="response") {
                return $this->response($data, 200);
            }
        }
        return $this->response(
            array(
                "error" => $message
            ), $code);
    }

    public function updateAction()
    {
        return $this->response(
            array(
                "error" => "Update error"
            ), 400);
    }

    public function deleteAction()
    {
        return $this->response(
            array(
                "error" => "Delete error"
            ), 500);
    }

    public static function getRideshareQuote($db, $origin, $destination, $member_id, $transport_provider_id, $country) {
        $group_quote_id = 0;
        $trackedemail_item_id = 0;
        $prefill = 'f';
        $id = 0;
        list($message,$code,$action,$data) = QuoteApi::createReal(
            $db, $origin, $destination, $member_id, $transport_provider_id, 
            $group_quote_id, $trackedemail_item_id, $country, $prefill
        );
        if ($code==200) {
            // Do we have a cost? Most likely not now...
            if (is_array($data) && array_key_exists("cost",$data)) {
                return [$data["cost"],$data["id"]];
            }
            if ($action=="view") {
                // "View" quote
                sleep(5);
                $quote = QuoteApi::viewReal($db, $data["id"]);
                if (is_array($quote) && array_key_exists("cost",$quote)) {
                    return [$quote["cost"],$data["id"]];
                }
            }
            if (is_array($data) && array_key_exists("id",$data) && $data["id"]>0) {
                $id = $data["id"]; // No id - no way to check what is going on
                sleep(5); // wait a second...
                // Try to "view" quote
                $quote = QuoteApi::viewReal($db, $data["id"]);
                if (is_array($quote) && array_key_exists("cost",$quote)) {
                    return [$quote["cost"],$data["id"]];
                }
            }
        }
        return [0,$id];
    }

    public function checkQuote($id) {
	    return Quotes::checkQuote($id);
    }

    public function getQuoteDetails($id) {
    	return Quotes::getQuoteDetails($id);
    }

    /*
    curl -d '{
      "location_start":"Marina Bay Sands, 10 Bayfront Ave, Singapore 018956",
      "location_end":"97 Meyer Road, Singapore",
      "trackedemail_item_id":"15549",
      "transport_provider_id":"3",
      "location_start_lat":"1.2833754",
      "location_start_lng":"103.8607264",
      "location_end_lat":"1.2967624",
      "location_end_lng":"103.8927854"
    }' \
    -H "Authorization: Server-Token 99dfe35fcb7de1ee" -H "Content-Type: application/json" \
    -X POST http://automation.float.sg/api/automation-job
    */
    public function scheduleQuote($data) {
        syslog(LOG_WARNING,'QuoteApi::scheduleQuote($data)');
        global $savvyext;
        $httpAuthToken = $savvyext->cfgReadChar('system.automation_api_token');
        $automation_api_url = $savvyext->cfgReadChar('system.automation_api_url');

        $url = $automation_api_url."automation-job/";
        $opts = array(
            'http' => array(
                'method' => "POST",
                'header'  => 
                    "Content-Type: application/json\r\n" .
                    "Accept: application/json\r\n" .
                    "Authorization: Server-Token ${httpAuthToken}\r\n",
                'content' => json_encode($data)
            ),
            "ssl" => array(
              "verify_peer"=>false,
              "verify_peer_name"=>false,
            )
        );
        $context = stream_context_create($opts);
        $body = file_get_contents($url, false, $context);
        $schedule = json_decode($body,true);
        if (is_array($schedule) && isset($schedule["id"])) {
            return array($schedule, NULL);
        } else if (is_array($schedule) && isset($schedule["message"])) {
            $body = $schedule["message"];
            syslog(LOG_WARNING,isset($schedule["error"]) ? json_encode($schedule["error"]) : $schedule["message"]);
        }
        syslog(LOG_WARNING,$body);
        return array(NULL, $body);
    }

}
