<?php

namespace CleantalkSP\SpbctWP\Scanner\CureLog;

use CleantalkSP\SpbctWP\DB;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\BackupsActions;

class CureLog
{
    /**
     * @var DB
     */
    private $db;
    public function __construct()
    {
        $this->db = DB::getInstance();
    }

    /**
     * Returns count of cured files in cure log table
     * @return int
     */
    public function getCountData()
    {
        //do not skip restored files!
        $query = 'SELECT COUNT(*) as cnt FROM ' . SPBC_TBL_CURE_LOG;
        $result = $this->db->fetch($query);
        return (int)$result->cnt;
    }

    /**
     * Returns cure log data for scanner accordion tab
     * @return array|object
     */
    public function getDataToAccordion($offset = 0, $amount = 20)
    {
        $offset = intval($offset);
        $amount = intval($amount);
        $query = '
                SELECT     fast_hash,
                           real_path, 
                           cure_status, 
                           cci_cured, 
                           weak_spots_cured, 
                           weak_spots_uncured, 
                           has_backup, 
                           fail_reason, 
                           last_cure_date,
                           is_restored
                FROM ' . SPBC_TBL_CURE_LOG . ' ORDER BY is_restored DESC, cure_status  LIMIT ' . $offset . ',' . $amount . ';';
        $result = $this->db->fetchAll($query, OBJECT);

        if ( empty($result) ) {
            return new \stdClass();
        }

        foreach ($result as $row) {
            if ( isset($row->cure_status) ) {
                if ( $row->cure_status == '1' ) {
                    $row->cure_status = 'CURED';
                } else if ( $row->cure_status == '0' ) {
                    $row->cure_status = 'FAILED';
                } else {
                    $row->cure_status = 'PARTIALLY CURED';
                }
            }

            if (isset($row->is_restored) && $row->is_restored == 1) {
                $row->cure_status = 'RESTORED';
            }

            if ( !empty($row->weak_spots_cured) && is_string($row->weak_spots_cured) ) {
                $template = '<div>';
                $template .= self::getThreatTemplate($row->weak_spots_cured, true);
                $template .= '</div>';
                $row->weak_spots_cured = $template;
            } else {
                $row->weak_spots_cured = '-';
            }

            if ( !empty($row->weak_spots_uncured) && is_string($row->weak_spots_uncured) ) {
                $template = '<div>';
                $template .= self::getThreatTemplate($row->weak_spots_uncured, false);
                $template .= '</div>';
                $row->weak_spots_uncured = $template;
            } else {
                $row->weak_spots_uncured = !empty($row->fail_reason) ? $row->fail_reason : '-';
            }

            if ( !empty($row->last_cure_date) ) {
                $row->last_cure_date = date("M d Y H:i:s", $row->last_cure_date);
            } else {
                $row->last_cure_date = 'N/A';
            }
        }
        return $result;
    }

    private static function getThreatTemplate($weak_spots_json, $is_cured = true)
    {
        $out = '';
        $array = json_decode($weak_spots_json, true);
        if ( !is_array($array) ) {
            $array = array();
        }

        $details_class = $is_cured ? 'spbc-icon-ok spbc---green' : 'spbc-icon-cancel spbc---red';

        $out .= count($array)
            ? '<span>' . __('Total', "security-malware-firewall") . ': <b>' . count($array) . '</b> </span>'
            : __('-', "security-malware-firewall");


        foreach ($array as $data) {
            $line = isset($data['line']) ? $data['line'] : '';
            $signature_id = isset($data['signature_id']) ? $data['signature_id'] : '';
            $error = isset($data['error']) ? $data['error'] : '';
            $out .= sprintf(
                '
                    <div style="border-top: 1px dashed gray; border-bottom: 1px dashed gray; margin-bottom: 2px">
                        <p style="font-size: smaller; color: gray; margin: 2px 0"><span class="' . $details_class . '"></span>'
                            . __('Code line number', "security-malware-firewall") . ': %s, '
                            . __('SID', "security-malware-firewall") . ': %s
                        <br>
                            <p style="font-size: smaller; color: gray; min-height: 25px; max-height: 25px; margin: 2px 0;">
                                <b>'
                                    . (!empty($error) ? __('Error:', "security-malware-firewall") : 'OK') . ' %s 
                                </b>
                            </p>
                        </p>
                    </div>
                    ',
                $line,
                $signature_id,
                $error
            );
        }
        return empty($out) ? '-' : $out;
    }

    /**
     * Returns cure log data for PDF report
     * @return array|object
     * @psalm-suppress PossiblyUnusedMethod
     */
    public function getDataToPDF()
    {
        $query = 'SELECT real_path, cure_status, cci_cured, last_cure_date 
            FROM ' . SPBC_TBL_CURE_LOG;
        $result = $this->db->fetchAll($query, ARRAY_A);

        if ( empty($result) ) {
            return array();
        }

        foreach ($result as &$row) {
            if ( isset($row->cure_status) ) {
                if ( $row->cure_status == '1' ) {
                    $row['cure_status'] = 'CURED';
                } else if ( $row->cure_status === '0' ) {
                    $row['cure_status'] = 'FAILED';
                } else {
                    $row['cure_status'] = 'PARTIALLY CURED';
                }
            }

            if ( !empty($row['last_cure_date']) ) {
                $row['last_cure_date'] = date("M d Y H:i:s", $row['last_cure_date']);
            } else {
                $row['last_cure_date']  = 'N/A';
            }
        }
        unset($row);
        return $result;
    }

    /**
     * Process cure log record.
     * @param CureLogRecord $cure_log_record
     * @return void
     */
    public function logCureResult(CureLogRecord $cure_log_record)
    {

        if (BackupsActions::fileHasBackup($cure_log_record->real_path)) {
            $cure_log_record->has_backup = 1;
        }

        $this->db->prepare(
            'INSERT INTO ' . SPBC_TBL_CURE_LOG
                . ' ('
                . '`fast_hash`,  '
                . '`full_hash`, '
                . '`cured_hash`, '
                . '`real_path`, '
                . '`cure_status`, '
                . '`is_restored`, '
                . '`weak_spots_cured`, '
                . '`weak_spots_uncured`, '
                . '`cci_cured`, '
                . '`has_backup`, '
                . '`fail_reason`, '
                . '`last_cure_date`, '
                . '`scanner_start_local_date`
                ) VALUES'
                . "(%s, %s, %s, %s, %d, %d, %s, %s, %d, %d, %s, %d, %s)"
                . 'ON DUPLICATE KEY UPDATE
                cure_status = VALUES(`cure_status`),
                is_restored = VALUES(`is_restored`),
                last_cure_date = VALUES(`last_cure_date`),
                fail_reason = VALUES(`fail_reason`),
                scanner_start_local_date = VALUES(`scanner_start_local_date`),
                cci_cured = VALUES(`cci_cured`),
                has_backup = VALUES(`has_backup`),
                fail_reason = VALUES(`fail_reason`),
                weak_spots_cured = VALUES(`weak_spots_cured`),
                weak_spots_uncured = VALUES(`weak_spots_uncured`),
                last_cure_date = VALUES(`last_cure_date`)',
            array($cure_log_record->fast_hash,
                $cure_log_record->full_hash,
                $cure_log_record->cured_hash,
                $cure_log_record->real_path,
                $cure_log_record->cure_status,
                $cure_log_record->is_restored,
                $cure_log_record->weak_spots_cured,
                $cure_log_record->weak_spots_uncured,
                $cure_log_record->cci_cured,
                $cure_log_record->has_backup,
                $cure_log_record->fail_reason,
                $cure_log_record->last_cure_date,
                $cure_log_record->scanner_start_local_date,
            )
        )->execute();
    }

    /**
     * @param $weak_spots_result
     * @return string[]
     */
    public static function parseWeakSpotsFromCureResult($weak_spots_result)
    {
        $cured = array();
        $uncured = array();
        foreach ($weak_spots_result as $_counter => $data) {
            $result_row = array(
                'line' => isset($data['weak_spots_file_line']) ? $data['weak_spots_file_line'] : '',
                'signature_id' => isset($data['signature_id']) ? $data['signature_id'] : '',
                'error' => isset($data['error']) ? $data['error'] : '',
            );
            if (isset($data['weakspot_is_cured']) && $data['weakspot_is_cured'] === 1) {
                $cured[] = $result_row;
            } else {
                $uncured[] = $result_row;
            }
        }
        $cured = json_encode($cured);
        $uncured = json_encode($uncured);
        if (empty($cured)) {
            $cured = '';
        }
        if (empty($cured)) {
            $uncured = '';
        }
        return array('cured' => $cured, 'uncured' => $uncured);
    }

    /**
     * @return array
     */
    public function getCuredFilesFastHashes()
    {
        $query = '
            SELECT fast_hash FROM ' . SPBC_TBL_CURE_LOG . '
            WHERE cure_status = 1;
        ';
        $result = $this->db->fetchAll($query);
        if (is_null($result) || is_object($result)) {
            $result = array();
        }
        return $result;
    }

    /**
     * @param $fast_hashes_only
     * @return array
     */
    public function getRestoredFiles($fast_hashes_only = false)
    {
        $query = '
            SELECT ' . ($fast_hashes_only ? 'fast_hash' : '*') . ' FROM ' . SPBC_TBL_CURE_LOG . '
            WHERE is_restored = 1;
        ';
        $result = $this->db->fetchAll($query);
        if (is_null($result) || is_object($result)) {
            $result = array();
        }
        return $result;
    }

    /**
     * Check if there is failed cure tries
     * @return bool
     */
    public function hasFailedCureTries()
    {
        // skip restored files!
        $query = 'SELECT COUNT(*) as cnt FROM ' . SPBC_TBL_CURE_LOG . ' WHERE cure_status <> 1 AND is_restored <> 1';
        $result = $this->db->fetch($query);
        return (bool)$result->cnt;
    }

    /**
     * Get totally failed files fast hashes. If nothing was cured at all.
     * @return array
     */
    public function getTotallyFailedFilesFastHashes()
    {
        $query = 'SELECT fast_hash FROM ' . SPBC_TBL_CURE_LOG . ' WHERE cure_status = 0';
        $result = $this->db->fetchAll($query);
        if (is_null($result) || is_object($result)) {
            $result = array();
        }
        return $result;
    }

    /**
     * Delete a cure log record by fast hash
     * @param $fast_hash
     * @return void
     */
    public function deleteCureLogRecord($fast_hash)
    {
        $query = 'DELETE FROM ' . SPBC_TBL_CURE_LOG . ' WHERE fast_hash = %s';
        $this->db->prepare($query, array($fast_hash))->execute();
    }

    /**
     * Delete a cure log and backup records by fast hash
     * @param $fast_hash
     * @return void
     */
    public function deleteCureLogAndBackupRecords($fast_hash)
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            'SELECT * FROM ' . SPBC_TBL_CURE_LOG . ' WHERE fast_hash = %s',
            $fast_hash
        );
        $cure_record = $wpdb->get_row($sql, ARRAY_A);

        if (empty($cure_record)) {
            return;
        }

        // delete cure log record
        $this->deleteCureLogRecord($fast_hash);

        // if file has backup, delete backup record and backup file
        if (isset($cure_record['has_backup']) && $cure_record['has_backup'] == 1) {
            BackupsActions::deleteLogAndFile($cure_record['real_path']);
        }
    }

    /**
     * Deletes cure log record if status is not cured but file has no signatures treatment
     * @return void
     */
    public function removeIrrelevantFailedFiles()
    {
        $query = 'DELETE FROM ' . SPBC_TBL_CURE_LOG .
                 ' WHERE cure_status = 0 and fast_hash IN
                    (
            		    SELECT fast_hash FROM ' . SPBC_TBL_SCAN_FILES . '
            		    WHERE weak_spots IS NULL OR weak_spots NOT LIKE "%SIGNATURES%"
				    );';
        $this->db->execute($query);
    }

    /**
     * Remove irrelevant restored files from cure log.
     * Deletes all the files that has current md5 differ from cured or full hash.
     * @return void
     */
    public function removeIrrelevantRestoredFiles()
    {
        $restored_files = $this->getRestoredFiles();
        if (empty($restored_files)) {
            return;
        }
        $to_remove = array();
        foreach ($restored_files as $restored_file) {
            if (
                !empty($restored_file['real_path']) &&
                !empty($restored_file['fast_hash']) &&
                !empty($restored_file['cured_hash']) &&
                !empty($restored_file['full_hash'])
            ) {
                $current_md5 = @md5_file(spbc_get_root_path() . $restored_file['real_path']);
                if ($current_md5 !== $restored_file['cured_hash'] && $current_md5 !== $restored_file['full_hash']) {
                    $to_remove[] = $restored_file['fast_hash'];
                }
            }
        }
        $this->db->execute(
            'DELETE FROM ' . SPBC_TBL_CURE_LOG . ' WHERE fast_hash IN ("' . implode('","', $to_remove) . '");'
        );
    }

    /**
     * @return array
     */
    public function getCureLogData()
    {
        // get cure log data
        $query = '
                    SELECT fast_hash, full_hash, cured_hash, cure_status, last_cure_date, is_restored
                    FROM ' . SPBC_TBL_CURE_LOG . '
                    GROUP BY fast_hash;
                    ';
        $result =  $this->db->fetchAll($query, OBJECT_K);

        if (is_null($result) || is_object($result)) {
            $result = array();
        }
        return $result;
    }
}
