<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 6/1/16
 * Time: 8:52 AM
 */

namespace IMATHUZH\Qfq\Core;

use IMATHUZH\Qfq\Core\Database\Database;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Store;


/**
 * Class Delete
 * @package qfq
 */
class Delete {
    /**
     * @var Database
     */
    private $db = null;

    /**
     * @var Store
     */
    private $store = null;
    private $evaluate = null;

    /**
     * @param bool $dbIndexData
     * @param bool $phpUnit
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public function __construct($dbIndexData = false, $phpUnit = false) {

        #TODO: rewrite $phpUnit to: "if (!defined('PHPUNIT_QFQ')) {...}"
        $this->db = new Database($dbIndexData);
        $this->store = Store::getInstance('', $phpUnit);
        $this->evaluate = new Evaluate($this->store, $this->db);
    }

    /**
     * Deletes the record id=$recordId from table $tableName.
     * If the table has a column named COLUMN_PATH_FILE_NAME and the value of that specific record column points
     * to a file: delete such a file if their are no other records in the same table which also have a reference to
     * that file.
     *
     * @param string $tableName
     * @param integer $recordId
     *
     * @param string $primaryKey
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \InfoException
     * @throws \UserReportException
     */
    public function process($tableName, $recordId, $primaryKey = F_PRIMARY_KEY_DEFAULT, $formElements = false) {

        if ($this->store->getVar(FLAG_MULTIFORM, STORE_SIP) == 'true') {
            $recordId = $this->store->getVar(MULTIFORM_DELETE_ROW_ID, STORE_SIP);
        }


        if ($tableName === false || $tableName === '') {
            throw new \CodeException('Missing table name', ERROR_MISSING_TABLE_NAME);
        }

        if ($recordId === 0 || $recordId === '') {
            throw new \CodeException('Invalid record id', ERROR_MISSING_RECORD_ID);
        }

        // Take care the necessary target directories exist.
        $cwd = getcwd();
        $sitePath = Path::absoluteApp();
        if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) {
            throw new \UserFormException(
                json_encode([ERROR_MESSAGE_TO_USER => 'getcwd() failed or SITE_PATH undefined or chdir() failed', ERROR_MESSAGE_TO_DEVELOPER => "getcwd() failed or SITE_PATH undefined or chdir('$sitePath') failed."]),
                ERROR_IO_CHDIR);

        }

        // Read record first.
        $row = $this->db->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_0_1, [$recordId]);
        if (count($row) > 0) {

            if ($formElements) {
                $this->deleteReferenceFilesForAdvancedUpload($formElements);
            }

            $this->deleteReferencedFiles($row, $tableName, $primaryKey);

            $this->db->sql("DELETE FROM `$tableName` WHERE `$primaryKey` =? LIMIT 1", ROW_REGULAR, [$recordId]);
        } else {
            throw new \UserFormException(
                json_encode([ERROR_MESSAGE_TO_USER => 'Record not found in table', ERROR_MESSAGE_TO_DEVELOPER => "Record $recordId not found in table '$tableName'."]),
                ERROR_RECORD_NOT_FOUND);

        }

        HelperFile::chdir($cwd);
    }

    /**
     * Iterates over array $row and searches for column names with substring COLUMN_PATH_FILE_NAME.
     * For any column found, check if it references a writable file.
     * If yes: check if there are other records (same table, same column) which references the same file.
     *         If no: delete the file
     *         If yes: do nothing, continue with the next column.
     * If no: do nothing, continue with the next column.
     *
     * @param array $row
     * @param       $tableName
     *
     * @param $primaryKey
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     */
    private function deleteReferencedFiles(array $row, $tableName, $primaryKey) {

        foreach ($row as $key => $file) {

            if (false === strpos($key, COLUMN_PATH_FILE_NAME)) {
                continue;
            }

            // check if there is a file referenced in the record which have to be deleted too.
            if ($file !== '' && is_writable($file)) {

                // check if there are other records referencing the same file: do not delete the file now.
                // This check won't find duplicates, if they are spread over different columns or tables.
                $samePathFileName = $this->db->sql("SELECT COUNT($primaryKey) AS cnt FROM `$tableName` WHERE `$key` LIKE ?", ROW_EXPECT_1, [$file]);
                if ($samePathFileName['cnt'] === 1) {
                    HelperFile::unlink($file);
                    $this->db->deleteSplitFileAndRecord($row[$primaryKey], $tableName);
                }
            }
        }
    }

    /**
     * Delete all reference files for multiUpload and Advanced Upload form elements.
     *
     * This function loops through an array of form elements, and for each upload type element
     * that is set as
     *
     * A: multiUpload with a valid uploadId, it retrieves the corresponding upload records from the database.
     *
     * or
     *
     * B: Advanced Upload with slaveId and sqlDelete Params, it parses and executes slaveId sql and the queries the DB for the slave Record.
     *
     * It then checks for the existence of each file and, if found, deletes the file from the filesystem and its record from the database.
     *
     * @throws \UserFormException
     * @throws \CodeException
     * @throws \InfoException
     * @throws \DbException
     * @throws \UserReportException
     */
    private function deleteReferenceFilesForAdvancedUpload($formElements): void {

        // Loop through each provided form element.
        foreach ($formElements as $formElement) {

            // Skip none upload elements
            if ($formElement[FE_TYPE] != FE_TYPE_UPLOAD) {
                continue;
            }

            HelperFormElement::explodeParameter($formElement, FE_PARAMETER);

            // Multi Upload
            // Proceed if this is a multiUpload element with a valid upload ID.
            if (isset($formElement[UPLOAD_TYPE]) && $formElement[UPLOAD_TYPE] === UPLOAD_MULTI_UPLOAD && isset($formElement[UPLOAD_ID])) {

                $table = $formElement[MULTI_UPLOAD_TARGET_TABLE] ?? MULTI_UPLOAD_TARGET_TABLE_DEFAULT;
                $uploadId = $this->evaluate->parse($formElement[UPLOAD_ID]);
                // If no upload id was given e.g 0 no files should be deleted.
                if ($uploadId == '0') {
                    continue;
                }
                $sql = "SELECT * FROM $table WHERE uploadId = ?";
                $uploads = $this->db->sql($sql, ROW_REGULAR, [$uploadId]);

                foreach ($uploads as $upload) {

                    $pathFileName = $upload[UPLOAD_PATH_FILE_NAME];
                    $uploadRecordId = $upload[COLUMN_ID];

                    if (file_exists($pathFileName)) {
                        HelperFile::unlink($pathFileName);
                        $deleteSql = "DELETE FROM $table WHERE id =? LIMIT 1";
                        $this->db->sql($deleteSql, ROW_REGULAR, [$uploadRecordId]);

                    } else {
                        throw new \InfoException('Cant delete previously uploaded file.');
                    }

                }
            }

            // Advanced Upload
            if (isset($formElement[FE_SLAVE_ID]) && isset($formElement[FE_SQL_DELETE])) {
                // Parse Slave ID
                $slaveId = Support::falseEmptyToZero($this->evaluate->parse($formElement[FE_SLAVE_ID]));
                $this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR);

                try {
                    // Extract Table Name From SQL delete
                    if (preg_match('/DELETE\s+FROM\s+([`"\[\]\w.]+)/i', $formElement[FE_SQL_DELETE], $matches)) {
                        $table = trim($matches[1], '`"[]');

                        // Assume that colum pathFileName exists
                        $sql = "SELECT pathFileName FROM $table WHERE id = ?";

                        // Execute SQL and ignore Unknown column Error Number 1054
                        $file = $this->db->sql($sql, ROW_IMPLODE_ALL, [$slaveId], skipErrno: [1054]);

                        // If a File was found unlink it
                        if (file_exists($file)) {
                            unlink($file);
                        }
                    }

                    // Execute sqlDelete regardless
                    $this->evaluate->parse($formElement[FE_SQL_DELETE]);
                } catch (\Exception $e) {
                    $this->evaluate->parse($formElement[FE_SQL_DELETE]);
                    throw $e;
                }
            }

        }

    }

}