<?php
/**
 * Data manipulation for the model
 */
 class CartModel{
     const PACKAGE_TYPES = [
         'ID_STANDARD_TYPE' => 1,
         'ID_OPTION_TYPE' => 2,
         'ID_ADDITONAL_TYPE' => 3
     ];

     const DOCUMENT_TYPES = [
         'ID_TEMPLATE_QUESIONNAIRE' => 1,
         'ID_CUSTOMER_QUESTIONNAIRE' => 2,
         'ID_TEMPLATE_AGREEMENT' => 6,
         'ID_CUSTOMER_AGREEMENT' => 7
     ];

     const PRODUCT_CATEGORIES = [
         'ITEMS' => 1,
         'INSTALLATION' => 2
     ];

     /**
      * get count of items in the cart
      * @return Object number of items in the cart
      */
     public function getCartCount(){
         global $database, $user;

         $sql = "SELECT COUNT(idPackage) as cartItemsCount
                 FROM ".TABLES['web_shop_cart']."
                 WHERE idUser=".$user->getUserId();

        $data = $database->fetchResultArray($sql);

        return  !empty($data) ? $data[0] : [];
     }

     /**
      * get items in cart
      * @return array list of items in the cart
      */
     public function getCartItems(){
         global $database, $user;
         $data = [
             'cartItems' => []
         ];
         $options = $this->getCartItemOptions();
         $additionalPackages = $this->getCartAdditionalPackages();
         $countries = new Countries();
         $packages = new Packages();
         $orderTypeInst = new OrderType();
         $interestRate = new InterestRate();
         $bidsHandler = new Bids();
         $bids = $bidsHandler->getCartBids();

         $sql = "SELECT
                    cart.id as idCart,
                    cart.idPackage,
                    cart.idCustomerInstance,
                    cart.idPrice,
                    cart.packageInstance,
                    cart.idBid as idSelectedBid,
                    p.status,
                    rclc.isLinkEnabled,
                    p.name as packageName,
                    cl.id AS idCommercialLead,
                    cl.name as commercialLead,
                    pt.payType,
                    pt.periodUnit AS periodUnit,
                    pt.id AS idPayType,
                    cart.quantity
                FROM ".TABLES['web_shop_cart']." cart
                INNER JOIN ".TABLES['packages']." p
                    ON p.id=cart.idPackage
                INNER JOIN ".TABLES['rel_commercial_lead_customers']." rclc
                    ON rclc.id=cart.idCustomerInstance
                INNER JOIN ".TABLES['commercial_leads']." cl
                    ON cl.id=rclc.idCommercialLead
                INNER JOIN ".TABLES['price_list_commercial_lead']." plcl
                    ON plcl.id=cart.idPrice
                INNER JOIN ".TABLES['payment_types']." pt
                    ON pt.id=plcl.idPaymentType
                INNER JOIN ".TABLES['price_list_broker']." plb
                    ON plb.idPackage=plcl.idPackage
                    AND plb.idPaymentType=plcl.idPaymentType
                WHERE cart.idUser=".$user->getUserId();
        $query = $database->query($sql);
        while($row = $database->fetchArray($query)){
            $prices = $packages->getPricesForPackages($row['idCommercialLead'], $row['idPackage'], self::PACKAGE_TYPES['ID_STANDARD_TYPE'], $row['idPayType']);
            if(count($prices[$row['idPackage']]) === 1){
                $price = $prices[$row['idPackage']][0];
                $row['fixedPrice'] = $price['fixedExtra'];
                $row['recurentPrice'] = $price['recurentExtra'];
                $row['servicesPrice'] = $price['servicesExtra'];

                $row['options'] = isset($options[$row['idCart']]) ? $options[$row['idCart']]['options'] : [];
                $row['areOptionsAvailable'] = isset($options[$row['idCart']]) ? $options[$row['idCart']]['areOptionsAvailable'] : true;
                $row['additionalPackages'] = isset($additionalPackages[$row['idCart']]) ? $additionalPackages[$row['idCart']]['additionalPackages'] : [];
                $row['areAdditionalAvailable'] = isset($additionalPackages[$row['idCart']]) ? $additionalPackages[$row['idCart']]['areAdditionalAvailable'] : true;
                $row['country'] = $countries->getCurrencyForPackage($row['idPackage']);
                $row['bids'] = isset($bids[$row['idPackage']]) ? $bids[$row['idPackage']] : [];
                $totalPackagePrice = $this->calculatePackageTotalPrice($row);
                $row['totalPrices'] = $totalPackagePrice;

                $data['cartItems'][] = $row;
            }
        }

        $data['totalPrice'] = $this->getCartTotalPrice($data['cartItems']);

        return $data;
     }

     /**
      * get options for items in the cart
      * @return Array list of options grouped by cart id
      */
     private function getCartItemOptions(){
         global $database, $user;
         $data = [];
         $packages = new Packages();

         $sql = "SELECT
                    wscep.idCart,
                    p.name AS packageName,
                    pog.name AS groupName,
                    rgo.idOptionPackage,
                    wsc.idPackage AS idParentPackage,
                    rclc.idCommercialLead AS idCommercialLead,
                    plcl.idPaymentType AS idPaymentType
                FROM ".TABLES['web_shop_cart_extra_packages']." wscep
                INNER JOIN  ".TABLES['packages']." p
                    ON p.id=wscep.idExtraPackage
                INNER JOIN ".TABLES['web_shop_cart']." wsc
                    ON wsc.id=wscep.idCart
                INNER JOIN ".TABLES['rel_commercial_lead_customers']." rclc
                    ON rclc.id=wsc.idCustomerInstance
                INNER JOIN ".TABLES['price_list_commercial_lead']." plcl
                    ON wsc.idPrice = plcl.id
                INNER JOIN ".TABLES['rel_group_options']." rgo
                    ON rgo.idOptionPackage=wscep.idExtraPackage
                INNER JOIN ".TABLES['package_option_groups']." pog
                    ON pog.id=rgo.idGroup AND pog.idPackage=wsc.idPackage
                WHERE wsc.idUser=".$user->getUserId();

         $query = $database->query($sql);
         while($row = $database->fetchArray($query)){
             $packageOptionPrices = $packages->getPricesForPackages($row['idCommercialLead'], $row['idParentPackage'], self::PACKAGE_TYPES['ID_OPTION_TYPE']);
             $allPrices = isset($packageOptionPrices[$row['idOptionPackage']]) ? $packageOptionPrices[$row['idOptionPackage']] : [];
             $isPaymentTypePresent = array_search($row['idPaymentType'], array_column($allPrices, 'idPaymentType'));
             $priceByPaymentTypeKey = $isPaymentTypePresent !== false ? $isPaymentTypePresent : -1;
             $row['prices'] = $priceByPaymentTypeKey > -1 ? $allPrices[$priceByPaymentTypeKey] : [];

             $row['isAvailable'] = count($row['prices']) > 0;
             $data[$row['idCart']]['options'][] = $row;
             if(!isset($data[$row['idCart']]['areOptionsAvailable'])){
                 $data[$row['idCart']]['areOptionsAvailable'] = true;
             }
             $data[$row['idCart']]['areOptionsAvailable'] = ($data[$row['idCart']]['areOptionsAvailable'] && $row['isAvailable']);
         }


         return $data;
     }

     /**
      * returns the additional packages from the cart
      * @return [type] [description]
      */
     private function getCartAdditionalPackages(){
         global $database, $user;
         $packages = new Packages();

         $data = [];
         $sql = "SELECT
                    wscep.idCart,
                    p.name AS packageName,
                    wsc.idPackage AS idParentPackage,
                    rclc.idCommercialLead AS idCommercialLead,
                    plcl.idPaymentType AS idPaymentType,
                    rap.idAdditionalPackage AS idAdditionalPackage
                FROM ".TABLES['web_shop_cart_extra_packages']." wscep
                INNER JOIN  ".TABLES['packages']." p
                    ON p.id=wscep.idExtraPackage
                INNER JOIN ".TABLES['web_shop_cart']." wsc
                    ON wsc.id=wscep.idCart
                INNER JOIN ".TABLES['rel_commercial_lead_customers']." rclc
                    ON rclc.id=wsc.idCustomerInstance
                INNER JOIN ".TABLES['price_list_commercial_lead']." plcl
                    ON wsc.idPrice = plcl.id
                INNER JOIN ".TABLES['rel_additional_packages']." rap
                    ON p.id=rap.idAdditionalPackage AND rap.idPackage=wsc.idPackage
                WHERE wsc.idUser=".$user->getUserId()." AND p.idPackageType=".self::PACKAGE_TYPES['ID_ADDITONAL_TYPE'];
         $query = $database->query($sql);
         while($row = $database->fetchArray($query)){
             $additionalPackagesPrices = $packages->getPricesForPackages($row['idCommercialLead'], $row['idParentPackage'], self::PACKAGE_TYPES['ID_ADDITONAL_TYPE']);
             $allPrices = isset($additionalPackagesPrices[$row['idAdditionalPackage']]) ? $additionalPackagesPrices[$row['idAdditionalPackage']] : [];
             $isPaymentTypePresent = array_search($row['idPaymentType'], array_column($allPrices, 'idPaymentType'));
             $priceByPaymentTypeKey = $isPaymentTypePresent !== false ? $isPaymentTypePresent : -1;
             $row['prices'] = $priceByPaymentTypeKey > -1 ? $allPrices[$priceByPaymentTypeKey] : [];

             $row['isAvailable'] = count($row['prices']) > 0;
             $data[$row['idCart']]['additionalPackages'][] = $row;
             if(!isset($data[$row['idCart']]['areAdditionalAvailable'])){
                 $data[$row['idCart']]['areAdditionalAvailable'] = true;
             }
             $data[$row['idCart']]['areAdditionalAvailable'] = ($data[$row['idCart']]['areAdditionalAvailable'] && $row['isAvailable']);
         }

         return $data;
     }

     /**
      * calculates and returns the total price for the order
      * @param  Array $cartItems all teh items in the cart with the details
      * @return String            total and recurrent price with it's currency
      */
     private function getCartTotalPrice($cartItems) {
         $totalPrice = 0;
         $totalRecurrentPrice = 0;
         $currency = '';
         $periodUnit = '';

         foreach($cartItems as $item) {
             $itemTotalPrice = $item['fixedPrice'];
             $itemTotalRecurrentPrice = $item['recurentPrice'] + $item['servicesPrice'];

             if(count($item['options'])) {
                 foreach($item['options'] as $packageOption) {
                     if(count($packageOption['prices'])) {
                         $itemTotalPrice += (float) $packageOption['prices']['fixedExtra'];
                         $itemTotalRecurrentPrice += (float) $packageOption['prices']['recurentExtra'] + (float) $packageOption['prices']['servicesExtra'];
                     }
                 }
             }

             if(count($item['additionalPackages'])) {
                 foreach($item['additionalPackages'] as $additionalPackage) {
                     if(count($additionalPackage['prices'])) {
                         $itemTotalPrice += (float) $additionalPackage['prices']['fixedExtra'];
                         $itemTotalRecurrentPrice += (float) $additionalPackage['prices']['recurentExtra'] + (float) $additionalPackage['prices']['servicesExtra'];
                     }
                 }
             }
             $itemTotalPrice *= $item['quantity'];
             $itemTotalRecurrentPrice *= $item['quantity'];

             $totalPrice += $itemTotalPrice;
             $totalRecurrentPrice += $itemTotalRecurrentPrice;
             $currency = $item['country']['currency'];
             $periodUnit = $item['periodUnit'];
         }

         return $totalPrice . ' ' . $currency . ' ( '. $totalRecurrentPrice . ' ' . $currency . ' / ' . $periodUnit .' )';
     }

     /**
      * update the quantity in the cart for an item
      * @param  INT $idPackage          id for the package
      * @param  INT $idCustomerInstance id for the customer instance based on linking to cl
      * @param  INT $idPrice            id for the price
      * @param  INT $quantity           quantity value
      * @return array                   update message
      */
     public function updateQuantity($idPackage, $idCustomerInstance, $idPrice, $quantity){
         global $database, $user;
         $data = [];

         $idPackage = $database->escapeValue($idPackage);
         $idPrice = $database->escapeValue($idPrice);
         $idCustomerInstance = $database->escapeValue($idCustomerInstance);
         $quantity = $database->escapeValue($quantity);

         if($database->invalidNumber('QUANTITY', $quantity, 1, 100)){
             $err_mes = [
                 'code' => 'error',
                 'message' => 'INVALID_QUANTITY'
             ];
             $data['messages'][] = $err_mes;

             return $data;
         }

         $sqlUpd = "UPDATE ".TABLES['web_shop_cart']."
                SET quantity=$quantity
                WHERE idPackage=$idPackage
                    AND idPrice=$idPrice
                    AND idCustomerInstance=$idCustomerInstance
                    AND idUser=".$user->getUserId()."
                ";
         $query = $database->query($sqlUpd);

         if($database->affectedRows() !== 1){
             $err_mes = [
                 'code' => 'warning',
                 'message' => 'NO_CHANGE'
             ];
             $data['messages'][] = $err_mes;

             return $data;
         }

         $mes = [
             'code' => 'success',
             'message' => 'QUANTITY_UPDATED'
         ];
         $data['messages'][] = $mes;

         return $data;
     }

     /**
      * remove item from cart
      * @param  INT $idCart          id for the cart
      * @return array                update message
      */
     public function removeFromCart($idCart){
         global $database, $user;
         $data = [];

         $idCart = $database->escapeValue($idCart);

         $sql = "DELETE FROM ".TABLES['web_shop_cart_extra_packages']."
                 WHERE idCart=$idCart ";
         $query = $database->query($sql);

         $sql = "DELETE FROM ".TABLES['web_shop_cart']."
                    WHERE id=$idCart
                    AND idUser=".$user->getUserId();

         $query = $database->query($sql);
         if($database->affectedRows() !== 1){
             $err_mes = [
                 'code' => 'error',
                 'message' => 'INVALID_PACKAGE_FOR_REMOVE'
             ];
             $data['messages'][] = $err_mes;
         }else{
             $message = [
                 'code' => 'success',
                 'message' => 'PACKAGE_REMOVED_FROM_CART'
             ];
             $data['messages'][] = $message;
         }

         return $data;
     }

     /**
      * upload questionaires for placing an order
      * @param  Array $file         uploaded file
      * @param  String $idDocumentType type of the document
      * @param  String $documentName the name to be set for the file in database
      * @param  INT $idPackage    id for the package
      * @return Array               upload message
      */
     public function uploadOrderDocument($file, $idDocumentType, $idPackage){
         global $database, $user;
         $isReUpload = false;

         $fileManager = new FileManager();
         $documentField = '';
         if(intval($idDocumentType) === self::DOCUMENT_TYPES['ID_CUSTOMER_QUESTIONNAIRE']){
             $documentField = 'idDocument';
             $documentName = 'customerQuestionaire_';
         }else{
             $documentField = 'idAgreementDocument';
             $documentName = 'customerAgreement_';
         }
         $documentName .= $idPackage.'_'.date('Y_m_d');

         $sql = "SELECT $documentField AS idDocument
                 FROM ".TABLES['web_shop_cart']."
                 WHERE idUser=".$user->getUserId()." AND idPackage=$idPackage AND $documentField IS NOT NULL";

         $documents = $database->fetchResultArray($sql);
         if(count($documents) > 0){
             $document = $documents[0];
             $data = $fileManager->updateDocument($document['idDocument'], $file);
             $isReUpload = true;
         }else{
             $uploadedBy = $user->getUserId();
             $data = $fileManager->uploadFile($file, $idDocumentType, $documentName, $uploadedBy);
             $isReUpload = false;
         }

         if(isset($data['messages'])){
             return $data;
         }

         if(!$isReUpload){
             $idDocument = $data['idDocument'];

             $sql = "UPDATE ".TABLES['web_shop_cart']."
                     SET $documentField=$idDocument
                     WHERE idUser=".$user->getUserId()." AND idPackage=$idPackage";
             $query = $database->query($sql);

             if($database->affectedRows() > 0){
                 $data['messages'][] = [
                     'code' => 'success',
                     'message' => 'FILE_UPLOADED'
                 ];
             }else{
                 $data['messages'][] = [
                     'code' => 'error',
                     'message' => 'NOT_LINKED_TO_CART'
                 ];
             }
         }else{
             $data['messages'][] = [
                 'code' => 'success',
                 'message' => 'FILE_UPLOADED'
             ];
         }

         return $data;
     }

     /**
      * get files that have been uploaded before palcing an order and are found in the cart
      * @param  INT $packages id of the package
      * @return Array         array of uploaded documetns for a package
      */
     private function getUploadedFilesForOrder($packages){
         global $database, $user;
         $data = [];

         $sql = "SELECT
                     doc.id as idDocument,
                     doc.documentName,
                     doc.extension,
                     doc.idDocumentType,
                     wsc.idPackage
                 FROM ".TABLES['documents']." doc
                 INNER JOIN ".TABLES['web_shop_cart']." wsc
                     ON (doc.id=wsc.idDocument OR doc.id=wsc.idAgreementDocument) AND wsc.idUser=doc.uploadedBy
                 WHERE wsc.idPackage IN($packages) AND wsc.idUser=".$user->getUserId()."";
         $query = $database->query($sql);
         while($row = $database->fetchArray($query)){
            $data[$row['idDocumentType']][$row['idPackage']] = $row;
         }

         return $data;
     }

     /**
      * get required template documents to place an order
      * @param  Array $packages ids for the packages in the cart
      * @return Array           array of documents
      */
     private function getTempalteDocuments($packages){
         global $database;

         $data = [];
         $sql = "SELECT
                      d.id as idDocument,
                      d.documentName,
                      d.documentPath,
                      d.extension,
                      p.id AS idPackage,
                      p.name AS packageName,
                      d.idDocumentType
                  FROM  ".TABLES['documents']." d
                  INNER JOIN ".TABLES['rel_package_documents']." rpd
                      ON rpd.idDocument=d.id
                  INNER JOIN ".TABLES['packages']." p
                      ON p.id=rpd.idPackage
                  WHERE rpd.idPackage IN($packages)
                      AND d.idDocumentType IN(".self::DOCUMENT_TYPES['ID_TEMPLATE_QUESIONNAIRE'].", ".self::DOCUMENT_TYPES['ID_TEMPLATE_AGREEMENT'].")";

         $query = $database->query($sql);
         while($row = $database->fetchArray($query)){
             $data[$row['idDocumentType']][$row['idPackage']] = $row;
             $idCustomerDocType = $row['idDocumentType'] == self::DOCUMENT_TYPES['ID_TEMPLATE_QUESIONNAIRE'] ? self::DOCUMENT_TYPES['ID_CUSTOMER_QUESTIONNAIRE'] : self::DOCUMENT_TYPES['ID_CUSTOMER_AGREEMENT'];
             $data['docsPackage'][$idCustomerDocType][$row['idPackage']] = $row;
         }

         return $data;
     }

     /**
      * get required documents to place an order
      * @param  Array $packages ids for the packages in the cart
      * @return Array           array of documents
      */
     public function getCartDocuments($packages){
         global $database;

         $data = [];
         $areFilesUploaded = true;
         $uploaded = [];
         $templates = [];
         $packages = implode(',', $packages);
         $packages = $database->escapeValue($packages);

         if($packages) {
             $uploaded = $this->getUploadedFilesForOrder($packages);
             $templates = $this->getTempalteDocuments($packages);
         }

         if(array_key_exists('docsPackage', $templates)) {
             foreach($templates['docsPackage'] as $idDocType => $templateDoc) {
                 foreach ($templateDoc as $idPackage => $packageDocDetails) {
                     if(!isset($uploaded[$idDocType]) || !isset($uploaded[$idDocType][$idPackage])){
                         $areFilesUploaded = false;
                         break;
                     }
                 }
             }
             unset($templates['docsPackage']);
         }

         return ['templates' => $templates, 'uploaded' => $uploaded, 'areFilesUploaded' => $areFilesUploaded];
     }

     /**
      * get details of the customer logged in
      * @return Array with neccessary details
      */
     public function getCustomerDetails() {
         global $database, $user;
         $userId = $user->getUserId();
         $data = [];
         $addresHelper = new AddressHelper();

         $sqlVatCode = "
                SELECT c.vatCode,
                       c.name AS companyName
                    FROM ".TABLES['company']." c
                INNER JOIN ".TABLES['users']." u
                    ON u.idCompany=c.id
                WHERE u.id = ".$userId."
                LIMIT 1";
        $result = $database->fetchResultArray($sqlVatCode);
        $data['vat'] = $result ? $result[0]['vatCode'] : '';
        $data['companyName'] = $result ? $result[0]['companyName'] : '';

        $data['deliveryAddresses'] = $addresHelper->getDeliveryAddress();
        $data['billing'] = $addresHelper->getBillingAddress();

         if($result = $this->getReferenceAndTender()) {
             $data['details'] = $result[0];
         }

         return ['customerDetails' =>  $data];
     }

      /**
       * returns the reference and tender if exists
       * @return Array reference and tender for the order
       */
      private function getReferenceAndTender() {
          global $database, $user;

          $sqlDetails = "
                 SELECT
                   reference,
                   tender,
                   idDeliveryAddress,
                   idBillingAddress,
                   idProject
                 FROM
                   ".TABLES['web_shop_cart']." wsc
                 WHERE wsc.idUser = ".$user->getUserId();

          return $database->fetchResultArray($sqlDetails);
      }

     /**
      * gets the country name for the id provided
      * @param  Array $data          all details
      * @param  String $addressType  delivery or billing address
      * @return String               name of the country
      */
     private function getCountryName($data, $addressType) {
         if(array_key_exists($addressType, $data)) {
             return $this->getCountryDetailsById($data[$addressType]['idCountrySelected'])[0]['countryName'];
         }

         return '';
     }

     /**
      * returns the name and code of the country by it's id
      * @param  int $idCountry id of the country
      * @return string            the name of the country
      */
     public function getCountryDetailsById($idCountry) {
         global $database;
         $countryName = '';

         $sql = "
                  SELECT name AS countryName,
                      UPPER(code) AS countryCode
                      FROM ".TABLES['countries']."
                  WHERE id=$idCountry
                  LIMIT 1";
          return $database->fetchResultArray($sql);
     }

     /**
      * get all the countries
      * @return Array country names and ids
      */
     public function getCountries() {
         global $database;

         $sql = "SELECT c.id, c.name
                  FROM ".TABLES['countries']." c";

        return ['countries' => $database->fetchResultArray($sql)];
     }

     /**
      * checks whether the informations for the order are correct
      * @param  Array $cartPackages the packages to be placed in the order
      * @param  Array $deliveryInfo the delivery information for the order
      * @param  Array $billingInfo  the billing information for the order
      * @return Array               error message ot empty
      */
     private function validateOrderDetails($cartPackages, $deliveryInfo, $billingInfo, $details) {
        global $database;

        if(count($cartPackages) < 1) {
            return [
                'code' => 'error',
                'message' => 'CART_EMPTY'
            ];
        }

        if(empty($deliveryInfo)){
            return [
                'code' => 'error',
                'message' => 'NO_DELIVERY_ADDRESS'
            ];
        }

        if(empty($billingInfo)){
            return [
                'code' => 'error',
                'message' => 'NO_BILLING_ADDRESS'
            ];
        }

        return [];
    }

    /**
     * adds or updates the delivery address for the user logged in
     * @param Array $deliveryInfo contaions the delivery address
     * @return Int affected rows
     */
    private function addUpdateOrderDeliveryAddress($deliveryInfo) {
        global $database, $user;

        foreach($deliveryInfo as $deliveryIndex => $detail) {
            $deliveryInfo[$deliveryIndex] = $database->escapeValue($detail);
        }

        $sql = "
             SELECT
              id
             FROM
              ".TABLES['delivery_addresses']."
             WHERE idUser = ".$user->getUserId()."
              AND idCountry = ".$deliveryInfo['idCountrySelected']."
              AND city = '".$deliveryInfo['city']."'
              AND detailedAddress = '".$deliveryInfo['detailedAddress']."'
              AND zip = '".$deliveryInfo['zipCode']."'
        ";
        $query = $database->query($sql);
        if($database->numRows($query) === 0) {
            $sqlDeliveryAddress = "
                    INSERT INTO ".TABLES['delivery_addresses']."
                        (idUser, idCountry, city, detailedAddress, zip)
                        VALUES (
                            ".$user->getUserId().",
                            ".$deliveryInfo['idCountrySelected'].",
                            '".$deliveryInfo['city']."',
                            '".$deliveryInfo['detailedAddress']."',
                            '".$deliveryInfo['zipCode']."'
                        )
            ";
            $query = $database->query($sqlDeliveryAddress);

            return $database->affectedRows();
        }

       return 0;
    }

    /**
     * adds or updates the billing information for the user
     * @param Array $billingInfo array of billing info - names and address
     * @return Int affected rows
     */
    private function addUpdateOrderBillingAddress($billingInfo) {
        global $database, $user;

        foreach($billingInfo as $billingIndex => $detail) {
            $billingInfo[$billingIndex] = $database->escapeValue($detail);
        }

        $sql = "
             SELECT
              id
             FROM
              ".TABLES['billing_information']."
             WHERE idUser = ".$user->getUserId()."
              AND idCountry = ".$billingInfo['idCountrySelected']."
              AND company = '".$billingInfo['companyName']."'
              AND firstName = '".$billingInfo['firstName']."'
              AND lastname = '".$billingInfo['lastName']."'
              AND city = '".$billingInfo['city']."'
              AND detailedAddress = '".$billingInfo['detailedAddress']."'
              AND zip = '".$billingInfo['zipCode']."'
        ";

        $query = $database->query($sql);
        if($database->numRows($query) === 0) {
            $sqlBillingAddress = "
                    INSERT INTO ".TABLES['billing_information']."
                        (idUser, idCountry, company, firstName, lastName, city, detailedAddress, zip)
                        VALUES (
                            ".$user->getUserId().",
                            ".$billingInfo['idCountrySelected'].",
                            '".$billingInfo['companyName']."',
                            '".$billingInfo['firstName']."',
                            '".$billingInfo['lastName']."',
                            '".$billingInfo['city']."',
                            '".$billingInfo['detailedAddress']."',
                            '".$billingInfo['zipCode']."'
                        )
            ";
            $query = $database->query($sqlBillingAddress);

            return $database->affectedRows();
        }

        return 0;
    }

     /**
      * returns the name and code of the country by it's id
      * @param  array $details  details of the order
      * @param  array $items     items from the cart
      * @return array            update messages
      */
     public function saveOrderDetails($details, $items) {
         global $database;
         $addressInserted = 0;
         $deliveryInfo = $details['delivery'];
         $billingInfo = $details['billing'];
         $orderDetails = $details['details'];
         $orderDetails['idDeliveryAddress'] = isset($deliveryInfo['id']) ?  $deliveryInfo['id'] : 'null';
         $orderDetails['idBillingAddress'] = isset($billingInfo['id']) ? $billingInfo['id'] : 'null';

         if($data['messages'][] = $this->validateOrderDetails($items, $deliveryInfo, $billingInfo, $orderDetails)) {
            return $data;
        }

        if($this->updateCartInfo($orderDetails) > 0) {
            $addressInserted++;
        }

        if($addressInserted) {
            $data['messages'][] = [
                'code' => 'success',
                'message' => 'ADDRESS_INSERTED'
            ];
        } else {
            $data['messages'][] = [
                'code' => 'warning',
                'message' => 'NO_ADDRESS_CHANGES'
            ];
        }

        return $data;
     }

     /**
      * updates the reference and tender for the order to be placed
      * @param  Array $details  reference and tender
      * @return Int             number of rows affected
      */
     private function updateCartInfo($details) {
         global $database, $user;

         foreach($details as $key => $detail) {
             $details[$key] = $database->escapeValue($detail);
         }

         $idProject = isset($details['idProject']) && $details['idProject'] ? $details['idProject'] : 'NULL';

         $sql = "
                 UPDATE
                  ".TABLES['web_shop_cart']."
                SET
                  reference = '".$details['reference']."',
                  tender = '".$details['tender']."',
                  idDeliveryAddress = '".$details['idDeliveryAddress']."',
                  idBillingAddress = '".$details['idBillingAddress']."',
                  idProject = $idProject
                WHERE idUser = ".$user->getUserId();
        $query = $database->query($sql);

        return $database->affectedRows();
     }

    /**
     * calculate the packages total price
     * @param  Array $cartPackage  the details for the package in the cart
     * @return float              the total price per package
     */
     private function calculatePackageTotalPrice($cartPackage){
         $totalPrice = [
             'fixedPrice' => 0,
             'recurrentPrice' => 0,
             'servicesPrice' => 0
         ];


         if(!empty($cartPackage['bids']) && $cartPackage['idSelectedBid']){
             $selectedBid = null;
             foreach ($cartPackage['bids'] as $bid) {
                 if($bid['idBid'] === $cartPackage['idSelectedBid']){
                     $selectedBid = $bid;
                     break;
                 }
             }

             $totalPrice['fixedPrice'] += $selectedBid ? $selectedBid['fixedPrice'] : $cartPackage['fixedPrice'];
             $totalPrice['recurrentPrice'] += $selectedBid ? $selectedBid['recurrentPrice'] : $cartPackage['recurentPrice'];
             $totalPrice['servicesPrice'] += $selectedBid ? $selectedBid['servicesPrice'] : $cartPackage['servicesPrice'];
         }else{
             $totalPrice['fixedPrice'] += $cartPackage['fixedPrice'];
             $totalPrice['recurrentPrice'] += $cartPackage['recurentPrice'];
             $totalPrice['servicesPrice'] += $cartPackage['servicesPrice'];
         }

         if(isset($cartPackage['options'])){
             foreach ($cartPackage['options'] as $option) {
                 if(count($option) && array_key_exists('prices', $option) && count($option['prices'])) {
                     $totalPrice['fixedPrice'] += $option['prices']['fixedExtra'];
                     $totalPrice['recurrentPrice'] += $option['prices']['recurentExtra'];
                     $totalPrice['servicesPrice'] += $option['prices']['servicesExtra'];
                 }
             }
         }

         if(isset($cartPackage['additionalPackages'])){
             foreach ($cartPackage['additionalPackages'] as $additionalPackage) {
                 if(count($additionalPackage) && array_key_exists('prices', $additionalPackage) && count($additionalPackage['prices'])) {
                     $totalPrice['fixedPrice'] += $additionalPackage['prices']['fixedExtra'];
                     $totalPrice['recurrentPrice'] += $additionalPackage['prices']['recurentExtra'];
                     $totalPrice['servicesPrice'] += $additionalPackage['prices']['servicesExtra'];
                 }
             }
         }

         return $totalPrice;
     }

     /**
      * genereate the order number using the id
      * @param  INT $idOrder id of the order
      * @return INT          order number containg the id value
      */
     private function generateOrderNumber($idOrder){
         return 1000000000 + $idOrder;
     }

     /**
      * adds the relation between order and packages
      * @param Array $orderPackages all the details for packages in order
      * @param Int $idOrder       the order id inserted
      * @return Int               the number of packages inserted
      */
     private function addOrderPackageRelation($orderPackages, $idOrder) {
         global $database;
         $insertValues = '';

         $sql = "
             INSERT INTO ".TABLES['rel_order_packages']."
                 (idOrder, idPackage, packageInstance, idPaymentTerm, units, packageFixedPrice, packageRecuringPrice, packageServicePrice, idBid)
             VALUES
         ";

         foreach($orderPackages as $packageInfo) {
             $totalPackagePrice = $this->calculatePackageTotalPrice($packageInfo);
             $isPriceInRange = $this->checkIfPriceInRange($totalPackagePrice);
             if($isPriceInRange) {
                 return $isPriceInRange;
             }

             $packageInfo['idSelectedBid'] = $packageInfo['idSelectedBid'] ? $packageInfo['idSelectedBid'] : 'NULL';

             $sqlPaymentType = "
                      SELECT id
                          FROM ".TABLES['payment_types']." pt
                      WHERE pt.payType='".$packageInfo['payType']."'";
             $result = $database->fetchResultArray($sqlPaymentType);
             $idPayType = $result && $result[0]['id'] ? $result[0]['id'] : 0;

             $insertValues .= "(
                                  $idOrder,
                                 '".$packageInfo['idPackage']."',
                                 '".$packageInfo['packageInstance']."',
                                 ".$packageInfo['idPayType'].",
                                 '".$packageInfo['quantity']."',
                                 '".$totalPackagePrice['fixedPrice']."',
                                 '".$totalPackagePrice['recurrentPrice']."',
                                  '".$totalPackagePrice['servicesPrice']."',
                                 ".$packageInfo['idSelectedBid']."
                             ),";
         }

         $insertValues = $insertValues ? rtrim($insertValues, ',') : '';
         if($insertValues) {
             $sql .= $insertValues;
             $query = $database->query($sql);

             return $database->affectedRows();
         }

        return 0;
     }

     /**
      * add order documents to relation table
      * @param Int $idOrder             id of the order
      * @param Int $idCustomerInstance  id customer instance
      * @return Int                     affected rows or 1
      */
     private function addOrderDocuments($idOrder, $idCustomerInstance){
         global $database, $user;
         $getDocSql = "SELECT wsc.idDocument, wsc.idAgreementDocument, wsc.idPackage
                        FROM ".TABLES['web_shop_cart']." wsc
                        WHERE idCustomerInstance=$idCustomerInstance";
         $query = $database->query($getDocSql);
         $vals = "";
         while($row = $database->fetchArray($query)){
             if($row['idDocument']){
                 $vals .= "(" . $idOrder . ", " . $row['idPackage'] . ", " . $row['idDocument'] . "),";
             }

             if($row['idAgreementDocument']){
                 $vals .= "(" . $idOrder . ", " . $row['idPackage'] . ", " . $row['idAgreementDocument'] . "),";
             }
         }
        ;
         if($vals){
             $vals = rtrim($vals, ',');
             $sql = "INSERT INTO ".TABLES['rel_order_documents']."
                    (idOrder, idPackage, idDocument)
                    VALUES $vals";
             $query = $database->query($sql);

             return $database->affectedRows();
         }

         return 1;
     }

     /**
      * add extra option for order packages
      * @param INT $idOrder id for the ORDER
      * @return INT         number of inserted elements
      */
     private function addOrderExtraPackages($idOrder){
         global $database, $user;

         $sql = "INSERT INTO ".TABLES['rel_order_extra_packages']."
                (idOrder, idPackage, idExtraPackage)
                SELECT $idOrder, wsc.idPackage, wscep.idExtraPackage
                FROM ".TABLES['web_shop_cart']." wsc
                INNER JOIN ".TABLES['web_shop_cart_extra_packages']." wscep
                    ON wsc.id=wscep.idCart
                WHERE wsc.idUser=".$user->getUserId();
         $query = $database->query($sql);

         return $database->affectedRows();
     }

     /**
      * remove the packages from the web shop cart after placing the order
      * @param  Int $idCustomerInstance id of the customer
      * @return Int                     number of rows deleted
      */
     private function removePackagesFromCartAfterOrder($idCustomerInstance) {
         global $database;

         $sql = "
              DELETE
              FROM
                ".TABLES['web_shop_cart']."
              WHERE idCustomerInstance = $idCustomerInstance";

          $result = $database->query($sql);

          return $database->affectedRows();
     }

     /**
      * adds the installation company in the selection relations table
      * @param Array $cartPackages  the packages from the cart
      * @param Int   $idOrder       the id of the order
      * @return Array with confirmation message
      */
     private function addInstallationCompanySelection($cartPackages, $idOrder) {
         global $database;
         $message = [];

         foreach($cartPackages as $position => $package) {
             $sql = "
                 SELECT
                    rpp.idProduct
                  FROM
                    ".TABLES['rel_package_products']." rpp
                    INNER JOIN ".TABLES['suppliers_countries_products']." scp
                      ON scp.idProduct = rpp.idProduct
                    INNER JOIN ".TABLES['product_categories']." pc
                      ON scp.idProductCategory = pc.id
                  WHERE pc.id = ".self::PRODUCT_CATEGORIES['INSTALLATION']."
                    AND rpp.idPackage = ".$package['idPackage']."
                    AND rpp.packageInstance = ".$package['packageInstance'];
              $products = $database->fetchResultArray($sql);
              if(count($products) === 1) {
                  $installationScheduling = new InstallationScheduling();
                  $message = $installationScheduling->changeInstallationCompany($idOrder, $package['idPackage'], $products[0]['idProduct']);
              }
         }

         return $message;
     }

     /**
      * adds the order with the whole details
      * @param  String $cartPackages Json string with object containing the packages to order
      * @param  String $details      Json string with object containing the details like refenrence or tender numbers
      * @return Array               confirmation message
      */
     public function placeOrder($cartItems, $orderDetails) {
        global $database, $user;
        $rowsAffected = 0;
        $deletedFromCart = 0;
        $totalFixedPrice = 0;
        $totalServicePrice = 0;
        $userId = $user->getUserId();
        $newCartPackages = [];
        $packageToDisplay = '';
        $unavailablePackageKey = '';
        $idPackages = '';
        $orderTypeInst = new OrderType();
        $clCustomersInst = new ClCustomers();

        $cartPackages = $cartItems;
        $deliveryInfo = $orderDetails['delivery'];
        $billingInfo = $orderDetails['billing'];
        $details = $orderDetails['details'];

        $options = $this->getCartItemOptions();
        $additionalPackages = $this->getCartAdditionalPackages();

        foreach($cartPackages as $packKey => $packageDetails) {
            $packageDetails = (array) $packageDetails;
            $areOptionsAvailable[$packageDetails['packageName']] = isset($options[$packageDetails['idCart']]) ? $options[$packageDetails['idCart']]['areOptionsAvailable'] : true;
            $areAdditionalAvailable[$packageDetails['packageName']] = isset($additionalPackages[$packageDetails['idCart']]) ? $additionalPackages[$packageDetails['idCart']]['areAdditionalAvailable'] : true;
            $packageNames[] = $packageDetails['packageName'];
            foreach($packageDetails as $packDetailKey => $packageDetail) {
                if(!is_array($packageDetail)){
                    $newCartPackages[$packKey][$packDetailKey] = $database->escapeValue($packageDetail);
                }else{
                    $newCartPackages[$packKey][$packDetailKey] = $packageDetail;
                }
            }
            $commercialLeadName = $packageDetails['commercialLead'];
            $idCommercialLead = $packageDetails['idCommercialLead'];
            $idPackages .= $packageDetails['idPackage'].',';
        }

        $unavailablePackageKey = $this->getNameIfPackageUnavailable($idPackages);
        $optionPackageKey = array_keys($areOptionsAvailable, false);
        if(count($optionPackageKey)) {
            $unavailablePackageKey = $optionPackageKey[0];
        } else {
            $additionalPackageKey = array_keys($areAdditionalAvailable, false);
            if(count($additionalPackageKey)) {
                $unavailablePackageKey = $additionalPackageKey[0];
            }
        }

        if($unavailablePackageKey) {
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'UNAVAILABLE_PACKAGE_IN_CART',
                'key' => $unavailablePackageKey
            ];

            return $data;
        }

        $cartPackages = $newCartPackages;
        foreach ($cartPackages as $position => $cartPackage) {
            $optionsToDisplay = '';
            if(isset($cartPackage['options'])){
                $optionsToDisplay .= '(';
                foreach ($cartPackage['options'] as $key => $option) {
                    $optionsToDisplay .= $option['groupName'] . ' : ' . $option['packageName'] .',';
                }
                $optionsToDisplay = rtrim($optionsToDisplay, ',');
                $optionsToDisplay .= ')';
            }

            if(isset($cartPackage['additionalPackages'])){
                $optionsToDisplay .= '(';
                foreach ($cartPackage['additionalPackages'] as $key => $option) {
                    $optionsToDisplay .= $option['packageName'] .', ';
                }
                $optionsToDisplay = rtrim($optionsToDisplay, ',');
                $optionsToDisplay .= ')';
            }
            $packageTotalPrices = $this->calculatePackageTotalPrice($cartPackage);

            $packageToDisplay .= ($position + 1).'. <b>'.$packageNames[$position].'</b>
                                    '.$optionsToDisplay.'
                                    <div class="mail-order-package-detail">
                                        <div>Commercial lead: '.$cartPackage['commercialLead'].'</div>
                                        <div>Payment type: '.$cartPackage['payType'].'</div>
                                        <div>Quantity: '.$cartPackage['quantity'].'</div>
                                        <label>'.$packageTotalPrices['fixedPrice'] * $cartPackage['quantity'].' ('.($packageTotalPrices['recurrentPrice'] + $packageTotalPrices['servicesPrice']) * $cartPackage['quantity'].' / '.$cartPackage['periodUnit'].')</label>
                                    </div> <br /><hr>
            ';

            $totalFixedPrice += $packageTotalPrices['fixedPrice'] * $cartPackage['quantity'];
            $totalServicePrice += ($packageTotalPrices['recurrentPrice'] + $packageTotalPrices['servicesPrice']) * $cartPackage['quantity'];
            $totalPeriodUnit = $cartPackage['periodUnit'];

        }
        $packageToDisplay .= 'Total Price: <b>'.$totalFixedPrice.' ('.$totalServicePrice.' / '.$totalPeriodUnit.')</b><br /><br />';

        foreach($deliveryInfo as $deliveryKey => $delivery) {
            $deliveryInfo[$deliveryKey] = $database->escapeValue($delivery);
        }

        foreach($billingInfo as $billingKey => $billing) {
            $billingInfo[$billingKey] = $database->escapeValue($billing);
        }

        foreach($details as $detailKey => $detail) {
            $details[$detailKey] = $database->escapeValue($detail);
        }

       $database->beginTransaction();

       $sqlCustomerInstance = "
            SELECT
                rclc.id AS idCustomerInstance
            FROM
                ".TABLES['rel_commercial_lead_customers']." rclc
            INNER JOIN ".TABLES['customers']." c
                ON c.id = rclc.idCustomer
                AND c.idUser = $userId
            INNER JOIN ".TABLES['commercial_leads']." cl
                ON cl.id = rclc.idCommercialLead
                AND cl.id = '".$idCommercialLead."'
            WHERE rclc.isLinkEnabled=1
            LIMIT 1";
        $result = $database->fetchResultArray($sqlCustomerInstance);
        if($result && $result[0]['idCustomerInstance']) {
            $idCustomerInstance = $result[0]['idCustomerInstance'];
        } else {
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'LINK_CUSTOMER_CL'
            ];
            $database->rollback();

            return $data;
        }

        $customerInfo = $clCustomersInst->getClCustomerInfo($idCommercialLead);
        $orderType = $customerInfo['idOrderType'] ? $customerInfo['idOrderType'] : $orderTypeInst->getCLDefaultOrderType($idCommercialLead);
        if($customerInfo['isSameCompanyAsCl'] == 1){
            $orderType = $orderTypeInst::ORDER_TYPES['reseller'];
        }

        $sqlOrderNumber = "
                SELECT
                  MAX(id) + 1 AS orderNumber
                FROM
                  ".TABLES['orders']."
                LIMIT 1";
        $result = $database->fetchResultArray($sqlOrderNumber);
        $orderNumber = $result && $result[0]['orderNumber'] ? $this->generateOrderNumber($result[0]['orderNumber']) : '1000000000';

        $today = new DateTime();
        $reference = array_key_exists('reference', $details) && $details['reference'] ? $details['reference'] : '';
        $tender = array_key_exists('tender', $details) && $details['tender'] ? $details['tender'] : '';
        $idProject = array_key_exists('idProject', $details) && $details['idProject'] ? $details['idProject'] : 'NULL';
        $countryDetails = $this->getCountryDetailsById($deliveryInfo['idCountrySelected'])[0];
        $countryName = $countryDetails['countryName'];
        $countryCode = $countryDetails['countryCode'];
        $concatenatedDeliveryAddress = $deliveryInfo['detailedAddress'].", ".$deliveryInfo['city'].", $countryName, ".$deliveryInfo['zipCode'];
        $billingCountryDetails = $this->getCountryDetailsById($billingInfo['idCountrySelected'])[0];
        $concatenatedBillingAddress = $billingInfo['detailedAddress'].", ".$billingInfo['city'].", ".$billingCountryDetails['countryName'].", ".$billingInfo['zipCode'];
        $projectNumber = $countryCode . $orderNumber;
        $orderDate = $today->format('Y-m-d H:m:s');

        $packageToDisplay .= '<b>Delivery address</b>: '.$concatenatedDeliveryAddress.'<br />';
        $packageToDisplay .= '<b>Billing address</b>: '.$concatenatedBillingAddress.'<br />';

        $sql = "SELECT MAX(t.id) as idTemrs
                FROM ".TABLES['terms']." t";
        $terms = $database->fetchResultArray($sql)[0];

        $sql = "
            INSERT INTO ".TABLES['orders']."
                (idCustomerInstance, orderNumber, orderDate, reference, tender, projectNumber, deliveryAddress, billingAddress, idTerms, idOrderType, idProject)
            VALUES (
                    $idCustomerInstance,
                    '".$orderNumber."',
                    '".$orderDate."',
                    '".$reference."',
                    '".$tender."',
                    '".$projectNumber."',
                    '".$concatenatedDeliveryAddress."',
                    '".$concatenatedBillingAddress."',
                    '".$terms['idTemrs']."',
                    '".$orderType."',
                    ".$idProject."
                )
        ";

        $query = $database->query($sql);

        if($database->affectedRows() === 1){
            $rowsAffected++;
        } else {
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'ERROR_ORDER_INSERT'
            ];
            $database->rollback();

            return $data;
        }

        $idOrder = $database->getInsertId();

        $relationData = $this->addOrderPackageRelation($cartPackages, $idOrder);
        if(is_array($relationData) || $relationData <= 0) {
            $data['messages'][] = is_array($relationData) ? $relationData : [
                'code' => 'error',
                'message' => 'ERROR_ORDER_PACK_RELATION'
            ];
            $database->rollback();

            return $data;
        } else {
            $rowsAffected++;
        }

        if($this->addOrderDocuments($idOrder, $idCustomerInstance) > 0) {
            $rowsAffected++;
        } else {
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'ERROR_ON_ADDING_DOCUMENTS'
            ];
            $database->rollback();

            return $data;
        }

        $addedOptions = $this->addOrderExtraPackages($idOrder);
        $deletedFromCart = $this->removePackagesFromCartAfterOrder($idCustomerInstance, $userId);
        if($this->addInstallationCompanySelection($cartPackages, $idOrder)) {
            $data['messages'][] = $this->addInstallationCompanySelection($cartPackages, $idOrder);
        }

        if($rowsAffected >= 3) {
            $database->commit();
            $data['messages'][] = [
                'code' => 'success',
                'message' => 'ORDER_PLACED'
            ];

            if($deletedFromCart > 0) {
                $data['messages'][] = [
                    'code' => 'success',
                    'message' => 'PACKAGES_DELETED'
                ];
            }

            $orderInfo['idOrder'] = $idOrder;
            $orderInfo['orderNumber'] = $orderNumber;
            $orderInfo['orderDate'] = $orderDate;

            $data['messages'][] = UtilsModel::sendOrderConfirmationMail($packageToDisplay, 'customer', $orderInfo);
            $data['messages'][] = UtilsModel::sendOrderConfirmationMail($packageToDisplay, 'broker', $orderInfo);
        } else {
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'ORDER_ERROR'
            ];
            $database->rollback();
        }

        return $data;
    }

    /**
     * return the name of the package unavailable or empty
     * @param  String $idPackages  all the ids of the packages from the cart
     * @return String              the name of the package unavailable or empty
     */
    private function getNameIfPackageUnavailable($idPackages) {
        global $database;
        if(!$idPackages) {
            return '';
        }
        $idPackages = rtrim($idPackages, ',');

        $sql = "SELECT
                  IF(p.status = 'available', 1, 0) AS isAvailable,
                  p.name
                FROM
                  ".TABLES['packages']." p
                WHERE p.id IN ($idPackages)";
        $query = $database->query($sql);

        while($row = $database->fetchArray($query)) {
            if(intval($row['isAvailable']) === 0) {
                return $row['name'];
            }
        }

        return '';
    }

    /**
     * checks if the prices have more than 15 digits
     * @return Bool true if all the prices have digits lower than 15
     */
    private function checkIfPriceInRange($packageTotalPrice) {
        global $database;
        $max_value = 1 * pow(10, 15);

        $fixPriceMessage = $database->invalidNumber('fixed price', $packageTotalPrice['fixedPrice'], 0, $max_value);
        if($fixPriceMessage) {
            return $fixPriceMessage;
        } else {
            $recurrentPriceMessage = $database->invalidNumber('recurrent price', $packageTotalPrice['recurrentPrice'], 0, $max_value);
            if($recurrentPriceMessage) {
                return $recurrentPriceMessage;
            } else {
                $servicePriceMessage = $database->invalidNumber('services price', $packageTotalPrice['servicesPrice'], 0, $max_value);

                return $servicePriceMessage ? $servicePriceMessage : null;
            }
        }

        return null;
    }

    public function setBidForCart($idBid, $idCart){
        $bidsHandler = new Bids();

        return $bidsHandler->setBidForCart($idBid, $idCart);
    }
 }
