<?php

/**
 *
 */
class ProcessesModel{
    const ID_PACKAGE_OPTION_TYPE = 2;

    function __construct(){
        $this->defaultSteps = [1, 2];
    }

    /**
     * Get and format info about processes
     * @param - optional string $nameToSearch is the name of the package or process to be searched
     * @return array - returns a formatted array with process details
     */
    public function getProcessInfo($nameToSearch = '') {
        global $database;
        $whereClause = '';
        $processInfo = [];

        if(!empty($nameToSearch)) {
            $whereClause = " WHERE
                                c.name LIKE '%".$database->escapeValue($nameToSearch)."%'
                                OR proc.name LIKE '%".$database->escapeValue($nameToSearch)."%' ";
        }

        $sql = "SELECT
                    proc.id AS idProcess,
                    proc.name AS processName,
                    ps.shortDesc AS processStep,
                    ps.isVisibleForCustomer AS isVisibleForCustomer,
                    c.id AS idCountry,
                    c.name AS countryName
                FROM
                    ".TABLES['process_step']." ps
                INNER JOIN ".TABLES['rel_process_steps']." rps
                    ON rps.idStep = ps.id
                INNER JOIN
                (
                    SELECT
                        rps_last.idProcess,
                        MAX(rps_last.processInstance) as lastInstace
                    FROM ".TABLES['rel_process_steps']." rps_last
                    GROUP BY rps_last.idProcess
                ) last_inst
                        ON last_inst.idProcess=rps.idProcess AND last_inst.lastInstace=rps.processInstance
                INNER JOIN ".TABLES['processes']." proc
                    ON proc.id = rps.idProcess
                INNER JOIN ".TABLES['countries']." c
                    ON c.id=proc.idCountry
                $whereClause
                ORDER BY processName, rps.idParent";

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

        while($row = $database->fetchArray($query)){
            if(!isset($processInfo[$row['idCountry']])){
                $processInfo[$row['idCountry']] = [
                    'idCountry' => $row['idCountry'],
                    'countryName' => $row['countryName']
                ];
            }

            if(!isset($processInfo[$row['idCountry']]['processes'][$row['idProcess']])){
                $processInfo[$row['idCountry']]['processes'][$row['idProcess']] = $row;
            }

            $processInfo[$row['idCountry']]['processes'][$row['idProcess']]['steps'][] = [
                'stepName' => $row['processStep'],
                'isVisibleForCustomer' => $row['isVisibleForCustomer']
            ];
        }

        return $processInfo;
    }

    /**
     * Get only the processes that match the wanted string
     * @param  string $nameToSearch is the name of the package or process to be searched
     * @return array - returns a formatted array with process details
     */
    public function searchProcess($nameToSearch) {
        if(empty($nameToSearch)) {
            return [
                'messageData'=> [
                        'status' => 'error',
                        'message' => 'SEARCH_EMPTY'
                ]
            ];
        }

        $processesFound = $this->getProcessInfo($nameToSearch);
        if($processesFound) {
            $data['data'] = $processesFound;
            $data['messageData'] = [
                'status' => 'success',
                'message' => 'SEARCH_SUCCESS'
            ];
        } else {
            $data['messageData'] = [
                'status' => 'warning',
                'message' => 'SEARCH_SUCCESS_EMPTY'
            ];
        }

        return $data;
    }

    /**
     * add or remove links between processes and package
     * @param  INT $idPackage         id of the package
     * @param  Array[Objects]  $selectedProcesses processes that need to be linked to the package
     * @return Array                    update message
     */
    public function linkProcessesToPackage($idPackage, $selectedProcesses){
        global $database;

        $idPackage = $database->escapeValue($idPackage);
        $selectedProcesses = json_decode($selectedProcesses);
        $data = [];
        $valuesSql = "";
        $notToDelSql = "";

        if($idPackage == 0){
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'NO_COUNTRY_OR_PACKAGE'
            ];

            return $data;
        }

        if(count($selectedProcesses) === 0){
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'NO_PROCESS_SELECTED'
            ];

            return $data;
        }

        foreach ($selectedProcesses as $process) {
            $idProcess = $database->escapeValue($process->idProcess);
            $valuesSql .= "(".$idPackage.",
                            ".$idProcess."
                        ),";
            $notToDelSql .= $idProcess . ',';
        }

        if($valuesSql !== "") {
            $valuesSql = rtrim($valuesSql, ',');
            $sql = "INSERT IGNORE INTO ".TABLES['rel_package_processes']."
                    (idPackage, idProcess)
                    VALUES
                    $valuesSql";
            $database->query($sql);
            $insertedProcesses = $database->affectedRows();

            $notToDelSql = rtrim($notToDelSql, ',');

            $sql = "DELETE FROM ".TABLES['rel_package_processes']."
                    WHERE idPackage=$idPackage AND idProcess NOT IN ($notToDelSql)";
            $database->query($sql);
            $deletedProcesses = $database->affectedRows();

            if($insertedProcesses < 1 && $deletedProcesses < 1){
                $data['messages'][] = [
                    'code' => 'warning',
                    'message' => 'NO_CHANGES_PROCESS'
                ];
            }else{
                $data['messages'][] = [
                    'code' => 'success',
                    'message' => 'PROCESSES_LINKED'
                ];
            }
        }

        return $data;
    }

    /**
     * get the list of packages that don't have processes associated
     * @return Array array of packages
     */
    public function getPackagesAndProcesses($idCountry = 0) {
        global $database;

        $sql = "SELECT
                    p.id AS idPackage,
                    p.name AS packageName
                FROM
                    ".TABLES['packages']." p
                LEFT OUTER JOIN ".TABLES['rel_package_processes']." rpp
                    ON rpp.idPackage=p.id
                WHERE p.idCountry = $idCountry AND p.idPackageType!=".self::ID_PACKAGE_OPTION_TYPE;

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

    /**
     * get info and steps for processes
     * @param  INT $idPackage   id for package (only to get also linked processes)
     * @return Array            list of steps and processes
     */
    public function getProcesses($idPackage){
        global $database;

        $idPackage = $database->escapeValue($idPackage);
        $data = [];

        $sql = "SELECT ps.shortDesc,
                       rps.idProcess,
                       ps.isVisibleForCustomer
                     FROM ".TABLES['process_step']." ps
                INNER JOIN ".TABLES['rel_process_steps']." rps
                    ON ps.id=rps.idStep
                INNER JOIN
                (
                    SELECT
                        rps_last.idProcess,
                        MAX(rps_last.processInstance) as lastInstace
                    FROM ".TABLES['rel_process_steps']." rps_last
                    GROUP BY rps_last.idProcess
                ) last_inst
                    ON last_inst.idProcess=rps.idProcess AND last_inst.lastInstace=rps.processInstance
                ORDER BY rps.idParent";
        $query = $database->query($sql);

        while($row = $database->fetchArray($query)){
            $data['steps'][$row['idProcess']][] = [
                'shortDesc' => $row['shortDesc'],
                'isVisibleForCustomer' => $row['isVisibleForCustomer']
            ];
        }

        $sql = "SELECT proc.id AS idProcess,
                proc.name AS processName,
                CASE WHEN rel_proc.idProcess IS NULL THEN '0' ELSE '1' END AS isSelected
                    FROM ".TABLES['processes']." proc
                LEFT OUTER JOIN
                (SELECT rp.idProcess
                   FROM ".TABLES['rel_package_processes']." rp
                   WHERE rp.idPackage=$idPackage
                 ) rel_proc
                 ON proc.id=rel_proc.idProcess
                ORDER by proc.name ASC";

        $data['processes'] = $database->fetchResultArray($sql);
        return $data;

    }

    /**
     * get processes names and ids for editing
     * @return array of ids and names for all available processes
     */
    public function getProcessNames() {
        global $database;

        $sql = "SELECT p.id,
                       p.idCountry,
                       p.name
                FROM " . TABLES['processes'] ." p
                ORDER BY p.name
        ";

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

    /**
     * get info and steps for processes available for editing
     * @param idProcess - the id of the process selected for editing
     * @return Array - list of steps and processes
     */
    public function getStepsForProcessSelected($idProcess, $getAllSteps = false){
        global $database;
        $data = [];
        $whereClause = '';
        $stepsInProcess = '';

        if($idProcess) {
            $whereClause = " AND rps.idProcess = $idProcess";
        }

        if(!$getAllSteps){
            $whereClause .= " AND idStep NOT IN(";
            foreach($this->defaultSteps as $idStep) {
                $whereClause .= $idStep.',';
            }
            $whereClause = rtrim($whereClause, ',') . ')';
        }

        foreach($this->defaultSteps as $idStep) {
            $stepsInProcess .= $idStep.',';
        }
        $sql = "SELECT
                        ps.id,
                        ps.shortDesc,
                       ps.isVisibleForCustomer,
                       rps.idStep AS idStep
                     FROM ".TABLES['process_step']." ps
                INNER JOIN ".TABLES['rel_process_steps']." rps
                    ON ps.id=rps.idStep
                INNER JOIN
                (
                    SELECT
                        rps_last.idProcess,
                        MAX(rps_last.processInstance) as lastInstace
                    FROM ".TABLES['rel_process_steps']." rps_last
                    GROUP BY rps_last.idProcess
                ) last_inst
                    ON last_inst.idProcess=rps.idProcess AND last_inst.lastInstace=rps.processInstance
                WHERE 1=1
                $whereClause
                ORDER BY rps.idParent";
        $query = $database->query($sql);

        while($row = $database->fetchArray($query)){
            $data['processSteps'][] = $row;
            $stepsInProcess .= $row['idStep'] . ',';
        }

        $stepsInProcess = $stepsInProcess ? rtrim($stepsInProcess, ',') : '';

        $sql = "SELECT proc.id AS idStep,
                       proc.shortDesc,
                       proc.isVisibleForCustomer
                FROM ".TABLES['process_step']." proc
                WHERE proc.id NOT IN ($stepsInProcess)
                ORDER by proc.shortDesc ASC";
        $query = $database->query($sql);

        while($row = $database->fetchArray($query)){
            $data['steps'][$row['idStep']] = [
                    'shortDesc' => $row['shortDesc'],
                    'isVisibleForCustomer' => $row['isVisibleForCustomer']
                ];
        }

        return $data;
    }

    /**
     * Edit a process - name and steps
     * @param $idProcess - the id of the process needed to be updated
     * @param $processData - the information regarding the process - name and steps
     * @return array - status message
     */
    public function editProcess($idProcess, $processData) {
        global $database;
        $idProcess = $database->escapeValue($idProcess);
        $processName = $database->escapeValue($processData['processName']);
        $idCountry = $database->escapeValue($processData['idCountry']);
        $steps = $processData['processSteps'];
        $data['idProcess'] = $idProcess;
        $changeFound = false;

        if($data['messageData'] = $this->validateEditProcessData($idProcess, $processName, $idCountry)) {
            return $data;
        }

        $sql = "UPDATE ".TABLES['processes']."
                    SET name='".$processName."', idCountry='".$idCountry."'
                WHERE id=$idProcess
        ";
        $result = $database->query($sql);

        if($database->affectedRows() > 0) {
            $data['messageData'][] = [
                'code' => 'success',
                'message' => 'PROCESS_NAME_UPDATED'
            ];
        }

        $originalProcessSteps = $this->getStepsForProcessSelected($idProcess, false)['processSteps'];
        if(count($originalProcessSteps) !== count($steps)){
            $changeFound = true;
        }else{
            for($i=0; $i<count($steps); $i++) {
                if($steps[$i]['idStep'] !== $originalProcessSteps[$i]['idStep']) {
                    $changeFound = true;
                }
            }
        }

        if(!$changeFound) {
            $data['messageData'][] = [
                'code' => 'warning',
                'message' => 'PROCESS_STEPS_NOT_CHANGED'
            ];
            return $data;
        }

        $idStepsUpdated = '';
        $idStepInserted = 0;

        $sql = "SELECT MAX(rps.processInstance) AS processInstance
                FROM ".TABLES['rel_process_steps']." rps
                WHERE idProcess=$idProcess
                LIMIT 1";
        $processInstance = $database->fetchResultArray($sql);
        $processInstance = isset($processInstance[0]['processInstance']) ? $processInstance[0]['processInstance'] + 1 : 1;

        $idStepInserted = $this->addProcessDefaultSteps($idProcess, $idStepInserted, $processInstance);

        foreach($steps as $index => $processStep) {
            $idStepInserted = $this->addProcessStepsInDb($processStep['idStep'], $idProcess, $idStepInserted, $processInstance);
        }

        if($idStepInserted > 0) {
            $data['messageData'][] = [
                'code' => 'success',
                'message' => 'PROCESS_STEPS_UPDATED'
            ];
        }

        return $data;
    }

    /**
     * validate the data required for a process
     * @param  INT $idProcess   id for the process
     * @param  String $processName name for the process
     * @param INT $idCountry      id for country
     * @return Array              error message array or null in case of success
     */
    private function validateEditProcessData($idProcess, $processName, $idCountry) {
        global $database;

        if($idProcess <= 0) {
            $message[] = [
                'code' => 'error',
                'message' => 'PROCESS_NOT_FOUND'
            ];
            return $message;
        }

        if(empty($idCountry) || $idCountry == 0){
            $message[] = [
                'code' => 'error',
                'message' => 'COUNTRY_MANDATORY'
            ];
            return $message;
        }

        $sql = "SELECT p.name
                    FROM " . TABLES['processes'] . " p
                WHERE p.name = '".$processName."'
                AND p.id <> $idProcess
        ";
        $result = $database->query($sql);

        if($database->affectedRows() > 0) {
            $message[] = [
                'code' => 'error',
                'message' => 'PROCESS_NAME_EXISTS'
            ];
            return $message;
        }

        return;
    }

    /**
     * Add process and relations to package and process steps in DB
     * @param  array $data contains the details for every step
     * @param  string $processName is the name of the process which will be added
     * @param int     $idCountry    io for the country
     * @return array - returns a failure or success message for adding the process in the DB
     */
    public function addProcess($data, $processName, $idCountry) {
        $messages['messageData'] = [];
        if(empty($data) || empty($processName) || empty($idCountry) || $idCountry == 0) {
            $messages['messageData'][] = [
                'code' => 'error',
                'message' => 'PROCESS_DATA_MISSING'
            ];

            return $messages;
        }

        if(strlen($processName) > 150) {
            $messages['messageData'][] = [
                'code' => 'error',
                'message' => 'STRING_TOO_LONG',
                'type' => 'Process name',
                'limit' => 150
            ];

            return $messages;
        }

        if($this->checkIfProcessExists($processName)) {
            $messages['messageData'][] = [
                'code' => 'error',
                'message' => 'PROCESS_NAME_EXISTS'
            ];

            return $messages;
        }

        $idProcess = $this->addProcessName($processName, $idCountry);
        $idStepInserted = 0;

        $idStepInserted = $this->addProcessDefaultSteps($idProcess, $idStepInserted, 1);

        foreach($data as $index => $processStep) {
            $idStepInserted = $this->addProcessStepsInDb($processStep->id, $idProcess, $idStepInserted, 1);
        }

        $messages['messageData'][] = [
            'code' => 'success',
            'message' => 'PROCESS_ADDED'
        ];

        return $messages;
    }

    /**
     * add the default steps for a process
     * @param INT $idProcess       id for the process
     * @param INT $idStepInserted  id of last insterted step
     * @param INT $processInstance instance of the process
     */
    private function addProcessDefaultSteps($idProcess, $idStepInserted, $processInstance){
        foreach($this->defaultSteps as $idProcessStep){
            $idStepInserted = $this->addProcessStepsInDb($idProcessStep, $idProcess, $idStepInserted, $processInstance);
        }

        return $idStepInserted;
    }

    /**
     * check if a process already exists
     * @param  String $processName process name
     * @return Boolean             returns true in case a process with this name is found
     */
    private function checkIfProcessExists($processName) {
        global $database;

        $sql = "SELECT
                    p.name
                FROM
                    ".TABLES['processes']." p
                WHERE
                    p.name = '".$database->escapeValue(trim($processName))."'
        ";
        $result = $database->query($sql);
        $numRows = $database->numRows($result);

        return $numRows > 0;
    }

    /**
     * Insert in the DB the process steps for the recently added process
     * @param  array or object $processStep contains the details and/or chidren for every step
     * @param  int $idProcess is the id of the process name added in the corresponding table
     */
    private function addProcessStepsInDb($idProcessStep, $idProcess, $idStepInserted = 0, $processInstance = 1) {
        global $database;

        $sql = "INSERT INTO ".TABLES['rel_process_steps']."
                    (idProcess, processInstance, idStep, idParent)
                VALUES(
                    ".$database->escapeValue($idProcess).",
                    ".$database->escapeValue($processInstance).",
                    ".$database->escapeValue($idProcessStep).",
                    ".$database->escapeValue($idStepInserted)."
                    )";

        $database->query($sql);
        return $database->getInsertId();
    }

    /**
     * Add process name in the corresponding table and return the inserted id
     * @param  string $processName is the name of the process which will be added
     * @param INT     $idCountry  id for country
     * @return int - returns the latest inserted process name
     */
    private function addProcessName($processName, $idCountry) {
        global $database;

        $sql = "INSERT INTO ".TABLES['processes']."
                    (name, idCountry)
                VALUES(
                    '".$database->escapeValue($processName)."',
                    '".$database->escapeValue($idCountry)."'
                    )";
        $result = $database->query($sql);
        return $database->getInsertId();
    }

    /**
     * Get existing process steps
     * @param  int $idPackage is the id of the package selected
     * @return int - returns number of products added in the package
     */
    public function getProcessSteps($idPackage) {
        $processSteps = $this->getProcessStepsFromDB();

        return [
            'steps'=> $processSteps,
            'process'=> []
        ];
    }

    /**
     * Get existing process steps from DB
     * @return array - returns array with process steps existing in DB
     */
    private function getProcessStepsFromDB() {
        global $database;

        $sql = "SELECT
                    proc.id, proc.shortDesc, proc.fullDesc, proc.idUserType, proc.isVisibleForCustomer
                FROM
                    ".TABLES['process_step']." proc
                WHERE proc.id NOT IN(1, 2)
                ORDER BY proc.shortDesc";

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

    /**
     * Add process step in DB
     * @param  string $shortDesc is the short description of the process step
     * @param  string $fullDesc is the full description of the process step
     * @param  int $idUserType is the id of the user type selected that is able to modify the step
     * @param int $isStepVisible is a flag for making the step visible or not for the customer
     * @param string $extraActionCode is the extra action name, if it is the case
     * @return array - returns a failure or success message for adding the process step in the DB
     */
    public function addProcessStep($shortDesc, $fullDesc, $idUserType, $isStepVisible, $extraActionCode) {
        global $database;
        $extraInsertFields = '';
        $extraInsertValues = '';

        if($shortDesc === '' || $fullDesc === ''){
            $data['messageData'][] = [
                'code' => 'error',
                'message' => 'DESC_MISSING'
            ];

            return $data;
        }

        if($idUserType == 0) {
            $data['messageData'][] = [
                'code' => 'error',
                'message' => 'USER_TYPE_MISSING'
            ];

            return $data;
        }

        if(strlen($shortDesc) > 100) {
            $data['messageData'][] = [
                'code' => 'error',
                'message' => 'STRING_TOO_LONG',
                'type' => 'Short description',
                'limit' => 100
            ];

            return $data;
        }

        if(strlen($fullDesc) > 500) {
            return [
                'messageData' => [
                    'code' => 'error',
                    'message' => 'STRING_TOO_LONG',
                    'type' => 'Full description',
                    'limit' => 500
                ]
            ];
        }

        if($this->checkIfProcessStepExists($shortDesc)) {
            $data['messageData'][] = [
                'code' => 'error',
                'message' => 'PROCESS_STEP_EXISTS'
            ];

            return $data;
        }

        if($extraActionCode) {
            $extraActionCode = $database->escapeValue($extraActionCode);
            $extraInsertFields = ', idActionCode';
            $extraInsertValues = ", '$extraActionCode'";
        }

        $sql = "INSERT INTO ".TABLES['process_step']."
                    (shortDesc, fullDesc, idUserType, isVisibleForCustomer $extraInsertFields)
                VALUES(
                    '".$database->escapeValue($shortDesc)."',
                    '".$database->escapeValue($fullDesc)."',
                    ".$database->escapeValue($idUserType).",
                    ".$database->escapeValue($isStepVisible)."
                    $extraInsertValues
                    )";
        $database->query($sql);

        $data['idInserted'] = $database->getInsertId();
        $data['messageData'][] = [
            'code' => 'success',
            'message' => 'STEP_ADDED'
        ];

        return $data;
    }

    /**
     * Check if processStep to be added already exists in the DB
     * @param  string $shortDesc is the short description of the process step
     * @return bool - returns true if the processStep already exists in the DB, false otherwise
     */
     private function checkIfProcessStepExists($shortDesc) {
         global $database;

         $sql = "SELECT
                    shortDesc
                FROM
                    ".TABLES['process_step']."
                WHERE
                    shortDesc = '".$database->escapeValue(trim($shortDesc))."'
        ";

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

        return $numRows > 0;
     }

     /**
      * Get all available user types
      * @return array - returns an array with all available user types (commercial lead, broker, customer)
      */
     public function getUserTypes() {
         global $database;

         $sql = "SELECT u.id, u.type
                 FROM ".TABLES['user_types']." u";

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

     /**
      * Get all available extra actions for processes
      * @return array - returns an array with all available extra actions
      */
     public function getExtraActionsAvailable() {
         global $database;

         $sql = "
                 SELECT
                     psa.id as idActionCode,
                     psa.actionCode
                 FROM
                     ".TABLES['process_step_actions']." psa
                 WHERE psa.stepType='extraAction'
                 ORDER BY actionCode";

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

}
