<?php

namespace QuickBooksOnline\API\Core;

use QuickBooksOnline\API\Core\Configuration\IppConfiguration;
use QuickBooksOnline\API\Core\Configuration\LocalConfigReader;
use QuickBooksOnline\API\Security\OAuthRequestValidator;
use QuickBooksOnline\API\Diagnostics\TraceLevel;
use QuickBooksOnline\API\Exception\SdkException;
use QuickBooksOnline\API\Core\Http\Compression\CompressionFormat;
use QuickBooksOnline\API\Core\Http\Serialization\SerializationFormat;
use QuickBooksOnline\API\Core\CoreConstants;
use QuickBooksOnline\API\Security\RequestValidator;
use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken;

/**
 * THe Service Context Class contains necessary settings for RestCalls
 */
class ServiceContext
{
    /**
     * The Realm Id.
     * Realm ID is the Company ID
     * @var string
     */
    public $realmId;

    /**
     * Intuit Service Type(QBO/QB).
     * @var string
     */
    public $serviceType;

    /**
     * Base Uri for IDS Service Call.
     * @var string
     */
    public $baseserviceURL;

    /**
     * Application Token.
     * @var string
     */
    private $appToken;

    /**
     * this flag indicates if static create methods of this class has been invoked.
     * @var string
     */
    private $isCreateMethod;

    /**
     * Temporary storage for serialization and compression values for request and reponse.
     * @var string
     */
    private $messageValues;

    /**
     * The Request validator
     * @var RequestValidator
     */
    public $requestValidator;

    /**
     * The Minor Version that would that returns additinoal fields from build v71
     * @var string
     */
    public $minorVersion;

    /**
     * Gets or sets the Ipp configuration.
     * @var IppConfiguration
     */
    public $IppConfiguration;


    /**
     * Keep this method for backward compability. Remove in next Release. USe Static factory to contruct new Instance();
     * @Feb.7th, 2017 Hao
     * Old Constructor from previous SDK code. Keep unchanged for backward compability.
     *   Initializes a new instance of the ServiceContext class.
     *
     * @param string $realmId The realm id.
     * @param string $serviceType Service Type - QBO/QB.
     * @param RequestValidator $requestValidator The request validate.
     * @throws IdsException If arguments are null or empty.
     * @throws InvalidRealmException If realm id is invalid.
     * @throws InvalidTokenException If the token is invalid.
     * @return ServiceContext Returns ServiceContext object.
     */
    private function __construct($realmId, $serviceType = CoreConstants::IntuitServicesTypeQBO, $requestValidator = null, $ippConfiguration = null)
    {
        if (isset($ippConfiguration)) {
            //$this->IppConfiguration = LocalConfigReader::ReadConfiguration();
            $this->IppConfiguration = $ippConfiguration;
        } else {
            throw new SdkException("You shouldn't pass a null ippConfiguration in Current release");
        }

        $this->realmId = $realmId;
        $this->serviceType = $serviceType;
        if ($requestValidator != null) {
            $this->IppConfiguration->Security = $requestValidator;
            $this->requestValidator = $requestValidator;
        } else {
            // Leave unchanged - already loaded from config file.
            $requestValidatorFromFile = $this->IppConfiguration->Security;
            $this->requestValidator = $requestValidatorFromFile;
        }

        $this->isCreateMethod = false;
        $this->baseserviceURL = $this->getBaseURL();

        return $this;
    }

    public static function ConfigureFromLocalFile($filePath)
    {
        $localIppConfiguration = LocalConfigReader::ReadConfigurationFromFile($filePath);
        $QBORealmID = $localIppConfiguration->RealmID;
        if (empty($QBORealmID)) {
            throw new SdkException('Sdk.config files contain empty RealmID.');
        }

        $serviceType = CoreConstants::IntuitServicesTypeQBO;
        $OAuthConfig = $localIppConfiguration->Security;
        $serviceContextInstance = new ServiceContext($QBORealmID, $serviceType, $OAuthConfig, $localIppConfiguration);

        return $serviceContextInstance;
    }

    /**
     * Configure the OAuth 1 or OAuth 2 values from a passed array
     * @param Array $settings    The array that contains OAuth 1 or OAuth 2 settings
     */
    public static function ConfigureFromPassedArray(array $settings)
    {
        ServiceContext::checkIfOAuthIsValid($settings);

        if(strcasecmp($settings['auth_mode'], CoreConstants::OAUTH1) == 0) {
             $OAuthConfig = new OAuthRequestValidator(
               $settings['accessTokenKey'],
               $settings['accessTokenSecret'],
               $settings['consumerKey'],
               $settings['consumerSecret']
             );
        }else{
             $OAuthConfig = new OAuth2AccessToken(
               $settings['ClientID'],
               $settings['ClientSecret'],
               CoreConstants::getAccessTokenFromArray($settings),
               CoreConstants::getRefreshTokenFromArray($settings)
             );
        }
        $QBORealmID = array_key_exists('QBORealmID', $settings) ? $settings['QBORealmID'] : null;
        $baseURL =    array_key_exists('baseUrl', $settings) ? $settings['baseUrl']: null;
        if(strcasecmp($baseURL, CoreConstants::DEVELOPMENT_SANDBOX) == 0){
           $baseURL = CoreConstants::SANDBOX_DEVELOPMENT;
        }else if(strcasecmp($baseURL, CoreConstants::PRODUCTION_QBO) == 0){
           $baseURL = CoreConstants::QBO_BASEURL;
        }
        $checkedBaseURL = ServiceContext::checkAndAddBaseURLSlash($baseURL);

        if($OAuthConfig instanceof OAuth2AccessToken){
           $OAuthConfig->setRealmID($QBORealmID);
           $OAuthConfig->setBaseURL($checkedBaseURL);
        }
        $serviceType = CoreConstants::IntuitServicesTypeQBO;
        $IppConfiguration = LocalConfigReader::ReadConfigurationFromParameters($OAuthConfig, $checkedBaseURL, CoreConstants::DEFAULT_LOGGINGLOCATION, CoreConstants::DEFAULT_SDK_MINOR_VERSION);
        $serviceContextInstance = new ServiceContext($QBORealmID, $serviceType, $OAuthConfig, $IppConfiguration);
        return $serviceContextInstance;
    }



    /**
     * Check if passed array has mininum required fields
     *
     * @param Array $settings        The array contain OAuth 1 or OAuth 2 settings
     */
    public static function checkIfOAuthIsValid(array $settings){
      if (!isset($settings) || empty($settings)) {
          throw new SdkException("Empty OAuth Array passed. Can't construct ServiceContext based on Empty Array.");
      }

      if(!isset($settings['auth_mode'])){
          throw new SdkException("No OAuth 1 or OAuth 2 Mode specified. Can't validate OAuth tokens.");
      }

      $mode = $settings['auth_mode'];

      //OAuth 1 settings, OAuth 1 must provide all four values.
      if (strcasecmp($mode, CoreConstants::OAUTH1) == 0) {
        if (!isset($settings['accessTokenKey'])) {
            throw new SdkException("'accessTokenKey' must be provided in OAuth1.");
        }

        if (!isset($settings['accessTokenSecret'])) {
            throw new SdkException("'accessTokenSecret' must be provided in OAuth1.");
        }
      }
      //OAuth 2 settings, Only client ID and Client Secret is required. For making API call, all four values, Client ID, Client Secret, Access Token, Access Token Secret are required.
      else if(strcasecmp($mode, CoreConstants::OAUTH2) == 0){
        if (!isset($settings['ClientID'])) {
            throw new SdkException("'ClientID' must be provided in OAuth2.");
        }

        if (!isset($settings['ClientSecret'])) {
            throw new SdkException("'ClientSecret' must be provided in OAuth2.");
        }
      }
      //Now other OAuth is supported
      else{
        throw new SdkException("OAuth Mode is not supported.");
      }
    }

    /**
     * When user passing the URL from Code. Sometimes they forget to add "/" at the end of string. Add it for them.
     * @param String $baseURL          return the formated string for the
     */
    private static function checkAndAddBaseURLSlash($baseURL){
        $lastChar = substr($baseURL, -1);
        if(strcmp($lastChar, "/") != 0){
             $baseURL = $baseURL . "/";
        }
        return $baseURL;
    }


    /**
     * Gets the base Uri for a QBO user.
     *
     * @return string Returns the base Uri endpoint for a user.
     */
    public function getBaseURL()
    {
        $this->IppConfiguration->Logger->RequestLog->Log(TraceLevel::Info, "Called GetBaseURL method.");
        try {
            if ($this->serviceType === CoreConstants::IntuitServicesTypeQBO) {
                $baseurl = $this->IppConfiguration->BaseUrl->Qbo . implode(CoreConstants::SLASH_CHAR, array(CoreConstants::VERSION)) . CoreConstants::SLASH_CHAR;
            }
            else if($this->serviceType === CoreConstants::IntuitServicesTypeIPP){
                $this->IppConfiguration->BaseUrl->Ipp  =  CoreConstants::IPP_BASEURL;
                $baseurl = $this->IppConfiguration->BaseUrl->Ipp;
            }
        } catch (\Exception $e) {
            throw new \Exception("Base URL is not setup");
        }
        return $baseurl;
    }

    /**
     * Populates the values of the service context like service type and base url to Platform Services.
     */
    public function UsePlatformServices()
    {
        $this->serviceType = CoreConstants::IntuitServicesTypeIPP;
        $this->baseserviceURL = $this->getBaseURL();
    }

    public function disableSSlCheck()
    {
        $this->IppConfiguration->SSLCheckStatus = false;
    }

    /**
     * Currently the Object serailziation and deserilization only supports XML format.
     */
    public function useXml()
    {
        $this->IppConfiguration->Message->Request->CompressionFormat = CompressionFormat::None;
        $this->IppConfiguration->Message->Response->CompressionFormat = CompressionFormat::None;
        $this->IppConfiguration->Message->Request->SerializationFormat = SerializationFormat::Xml;
        $this->IppConfiguration->Message->Response->SerializationFormat = SerializationFormat::Xml;
    }

    /**
     * Currently the Object serailziation and deserilization only supports XML format.
     * To be Supported, Currently it is only a place holder @Hao
     */
    public function useJson()
    {
        $this->IppConfiguration->Message->Request->CompressionFormat = CompressionFormat::None;
        $this->IppConfiguration->Message->Response->CompressionFormat = CompressionFormat::None;
        $this->IppConfiguration->Message->Request->SerializationFormat = SerializationFormat::Json;
        $this->IppConfiguration->Message->Response->SerializationFormat = SerializationFormat::Json;
    }

    /**
     * Disable Log the request and response to disk
     */
    public function disableLog()
    {
        try {
            $_ippConfigInstance = $this->getIppConfig();
            LocalConfigReader::setupLogger($_ippConfigInstance, CoreConstants::DEFAULT_LOGGINGLOCATION, "FALSE");
        } catch (\Exception $e) {
            throw new \Exception("Error in disable Log.");
        }
    }

    /**
     * Set a new Location for Log instead of the default Location
     * @param $new_log_location
     *        The new log directory path. It is a directory, not a file
     */
    public function setLogLocation($new_log_location)
    {
        try {
            $_ippConfigInstance = $this->getIppConfig();
            LocalConfigReader::setupLogger($_ippConfigInstance, $new_log_location, "TRUE");
        } catch (\Exception $e) {
            throw new \Exception("Error in setting up new Log Configuration: " . $new_log_location);
        }
    }

    public function setMinorVersion($new_minor_version)
    {
        try {
            $_ippConfigInstance = $this->getIppConfig();
            $_ippConfigInstance->minorVersion = $new_minor_version;
        } catch (\Exception $e) {
            throw new \Exception("Error in setting up minor version.");
        }
    }

    /**
     * Return the Ipp Configuration for the ServiceContext for changing the settings
     * @return IppConfiguration
     *            The IppConfiguration for current settings
     */
    private function getIppConfig()
    {
        $_ippConfiguration = $this->IppConfiguration;
        if (isset($_ippConfiguration)) {
            return $_ippConfiguration;
        } else {
            throw new \Exception("Return Null or Empty IppConfiguration.");
        }
    }

    /**
     * Update OAuth 2 Access Token with the new Token Value
     * @param OAuth2AccessToken  The OAuth 2 token that contains access token, refresh token
     */
    public function updateOAuth2Token($OAuth2AccessToken){
      if($OAuth2AccessToken instanceof OAuth2AccessToken && $this->requestValidator instanceof OAuth2AccessToken){
        $this->IppConfiguration->Security = $OAuth2AccessToken;
        $this->requestValidator = $OAuth2AccessToken;
      }

    }
}
