<?php

namespace CleantalkSP\SpbctWP\Scanner;

use CleantalkSP\SpbctWP\Scanner\CureLog\CureLog;

class Cure
{
    public $signature = null;
    public $objects = array();

    /**
     * @var array
     * @psalm-suppress PossiblyUnusedProperty
     */
    public $actions = array();

    /**
     * @var array
     * @psalm-suppress PossiblyUnusedProperty
     */
    public $modifiers = array();

    /**
     * @var array
     * @psalm-suppress PossiblyUnusedProperty
     */
    public $comments = array();

    public $result = true;

    public $weak_spots_result = array();

    public function __construct($file)
    {
        global $wpdb;

        $weak_spots = json_decode($file['weak_spots'], true);
        $counter = 0;

        if ( ! empty($weak_spots['SIGNATURES']) ) {
            foreach ( $weak_spots['SIGNATURES'] as $_string => $signatures_in_string ) {
                $this->weak_spots_result[$counter] = array(
                    'weak_spots_file_line' => $_string,
                    'signature_id' => '',
                    'weakspot_is_cured' => 0,
                    'error' => ''
                );
                foreach ( $signatures_in_string as $signature_id ) {
                    $this->weak_spots_result[$counter]['signature_id'] = $signature_id;
                    $tmp             = $wpdb->get_results(
                        'SELECT * FROM ' . SPBC_TBL_SCAN_SIGNATURES . ' WHERE id = "' . $signature_id . '"',
                        OBJECT
                    );
                    $this->signature = $tmp[0];
                    $result = $this->signatureCure($file, $this->signature, $this->weak_spots_result[$counter]);
                    $this->weak_spots_result[$counter]['weakspot_is_cured'] = (int)$result;
                }
                $counter++;
            }
        } else {
            $this->result = array('error' => 'COULD NOT GET SIGNATURE FROM DB');
        }
    }

    /**
     * @param $file
     * @param $signature
     * @param array $weak_spots_log
     * @return bool
     */
    public function signatureCure($file, $signature, &$weak_spots_log)
    {
        global $spbc;

        $cci_data = array();

        if ( ! empty($signature->cci) ) {
            $instructions = json_decode($signature->cci, true);
            if ($instructions && !empty($instructions['cci']) && is_array($instructions['cci'])) {
                $cci_data = $instructions['cci'];
            }
        }

        if ( ! empty($cci_data) ) {
            foreach ( $cci_data as $instruction ) {
                // Object
                foreach ( $instruction['objects'] as $_key => &$object ) {
                    // Building path to file

                    // Default if unset
                    if ( ! isset($object['file']) ) {
                        $object['file'] = 'self';
                    }

                    // self
                    if ( $object['file'] === 'self' ) {
                        $object['file'] = spbc_get_root_path() . $file['path'];
                        // absolute "/var/www/wordpress.com"
                    } elseif ( $object['file'][0] == '/' || $object['file'][0] == '\\' ) {
                        $object['file'] = spbc_get_root_path() . $object['file'];
                        // relative ".some_file.php"
                    } else {
                        $object['file'] = spbc_get_root_path()
                                          . preg_replace('/(.*\\\\).*?\.php$/', '$1', $file['path'])
                                          . $object['file'];
                    }

                    // Building code if nessary
                    if ( isset($object['code']) ) {
                        if ( $object['code'] === 'self' ) {
                            $object['code'] = $signature->body;
                        }
                    }

                    // Perform actions
                    if ( ! isset($object['code']) ) {
                        $result = $this->actionPerformWithFile(
                            $object,
                            $instruction['action'],
                            $file,
                            $signature->id
                        ); // Actions with file.
                    } else {
                        $result = $this->actionPerformWithCode(
                            $object,
                            $instruction['action'],
                            $file,
                            $signature->id
                        ); // Actions with code.
                    }

                    $this->objects[] = $object;

                    if ( ! empty($result['error']) ) {
                        $weak_spots_log['error'] = $result['error'];
                        return false;
                    }

                    if ( ! $spbc->settings['there_was_signature_treatment'] ) {
                        $spbc->settings['there_was_signature_treatment'] = 1;
                        $spbc->save('settings');
                    }
                }
            }
            return true;
        }
        $weak_spots_log['error'] = __('No cure instruction found for line.', 'security-malware-firewall');
        return false;
    }

    /**
     * @param $object
     * @param $actions
     * @param $file
     * @param string $signature_id
     *
     * @return array|bool|bool[]|false|int|string[]
     */
    public function actionPerformWithFile($object, $actions, $file, $signature_id)
    {
        global $spbc;

        $result = true;

        if ( ! file_exists($object['file']) ) {
            return array('error' => 'File does not exists.');
        }

        foreach ( $actions as $action => $action_details ) {
            switch ( $action ) {
                case 'delete':
                    $result = unlink($object['file']);
                    file_put_contents(
                        $object['file'],
                        "<?php\n// " . $spbc->data["wl_brandname"] . ": Malware was deleted: #" . $signature_id
                    );
                    break;
                case 'quarantine':
                    $result = spbc_scanner_file_quarantine(true, $file['fast_hash']);
                    break;
                case 'move_to':
                    /** @todo moveTo */
                    break;
                case 'replace_with':
                    if ( $action_details === 'original' ) {
                        $result = spbc_scanner_file_replace(true, $file['fast_hash']);
                    }
                    if ( $action_details === 'blank' ) {
                        $result = file_put_contents(
                            $object['file'],
                            '<?php\n/* File was cleaned by ' . $spbc->data["wl_brandname"] . ' */'
                        );
                    }
                    break;
            }
        }

        return $result;
    }

    /**
     * @param $object
     * @param $actions
     * @param $_file
     * @param $signature_id
     *
     * @return array|bool|int
     */
    public function actionPerformWithCode($object, $actions, $_file, $signature_id)
    {
        global $spbc;

        $result = true;

        try {
            foreach ( $actions as $action => $action_details ) {
                if ( file_exists($object['file']) ) {
                    $file_content = file_get_contents($object['file']);
                } else {
                    return array('error' => 'File does not exists.');
                }

                $is_regexp = \CleantalkSP\SpbctWP\Helpers\Helper::isRegexp($object['code']);

                switch ( $action ) {
                    case 'delete':
                        $file_content = $is_regexp
                            ? preg_replace(
                                $object['code'],
                                '/* ' . $spbc->data["wl_brandname"] . ': Malware was deleted: #' . $signature_id . '*/',
                                $file_content,
                                1
                            )
                            : str_replace(
                                $object['code'],
                                '/* ' . $spbc->data["wl_brandname"] . ': Malware was deleted: #' . $signature_id . '*/',
                                $file_content
                            );

                        $result = @file_put_contents($object['file'], $file_content);

                        break;

                    case 'replace_with':
                        $file_content = $is_regexp
                            ? preg_replace($object['code'], $action_details, $file_content, 1)
                            : str_replace($object['code'], $action_details, $file_content);

                        $result = @file_put_contents($object['file'], $file_content);

                        break;
                }
            }

            if ( false === $result ) {
                $result = array('error' => 'Permissions denied.');
            }
        } catch (\Exception $e) {
            $result = array('error' => 'Something went wrong during cure: ' . $e->getMessage());
        }
        return $result;
    }

    /**
     * Handles the AJAX request to cure selected files.
     *
     * This method is triggered via an AJAX request and attempts to cure the files
     * specified by their IDs in the `$_POST['selectedIds']` array. It performs the following steps:
     * 1. Verifies the AJAX nonce for security.
     * 2. Initializes response data.
     * 3. Checks if any IDs were provided.
     * 4. Retrieves the fast hashes of already cured files.
     * 5. Determines which files need to be cured.
     * 6. Attempts to cure each file and updates the response data accordingly.
     * 7. Sends a JSON response indicating success or failure.
     * 8. Response json is in the following format:
     * <pre>
     *  {
     *  'message' => 'Success!',
     *  'cured_on_request' => count of cured files,
     *  'skipped' => count of already cured files,
     *  'failed_to_cure' => count of failed files,
     * }
     * </pre>
     *
     * @return void
     */
    public static function cureSelectedAction()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $ids = isset($_POST['selectedIds']) ? $_POST['selectedIds'] : null;

        if (empty($ids)) {
            $response_data['message'] = esc_html__('No items to restore', 'security-malware-firewall');
            wp_send_json_error($response_data);
        }
        $cured_on_request = 0;
        $failed_to_cure = array();
        $skipped = 0;
        $response_data = array(
            'message' => esc_html__('Success!', 'security-malware-firewall'),
            'cured_on_request' => $cured_on_request,
            'skipped' => $skipped,
            'failed_to_cure' => $failed_to_cure,
        );

        $cure_stage = new CureLog();
        $cured_files = $cure_stage->getCuredFilesFastHashes();
        $cured_fast_hashes = array_column($cured_files, 'fast_hash');
        $to_cure = array_diff($ids, $cured_fast_hashes);

        if (empty($to_cure)) {
            $response_data['message'] = esc_html__('All selected files are already cured.', 'security-malware-firewall');
            wp_send_json_success($response_data);
        }

        $skipped = count($ids) - count($to_cure);

        foreach ($to_cure as $id) {
            $id = sanitize_user($id, true);
            $result = spbc_cure_file($id);

            if (is_wp_error($result)) {
                $path = spbc_scanner_get_file_by_id($id);
                $path = isset($path['path']) ? $path['path'] : 'unknown';
                $path = '..' . esc_html(substr($path, -50));
                $failed_to_cure[] = $path . ' - ' . $result->get_error_message();
            } else {
                $cured_on_request++;
            }
        }

        $response_data['cured_on_request'] = $cured_on_request;
        $response_data['skipped'] = $skipped;
        $response_data['failed_to_cure'] = $failed_to_cure;

        if (count($failed_to_cure) === 0) {
            wp_send_json_success($response_data);
        } else {
            $response_data['message'] = esc_html__('Some files were not cured.', 'security-malware-firewall');
            wp_send_json_error($response_data);
        }
    }

    public static function restoreSelectedAction()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $ids = isset($_POST['selectedIds']) ? $_POST['selectedIds'] : null;

        if (empty($ids)) {
            $response_data['message'] = esc_html__('No items to restore', 'security-malware-firewall');
            wp_send_json_error($response_data);
        }

        $restored_on_request = 0;
        $failed_to_restore = array();
        $skipped = 0;
        $response_data = array(
            'message' => esc_html__('Success!', 'security-malware-firewall'),
            'restored_on_request' => $restored_on_request,
            'skipped' => $skipped,
            'failed_to_restore' => $failed_to_restore,
        );

        $cure_log = new CureLog();
        $restored_fast_hashes = $cure_log->getRestoredFiles(true);
        $restored_fast_hashes = array_column($restored_fast_hashes, 'fast_hash');
        $to_restore = array_diff($ids, $restored_fast_hashes);
        if (empty($to_restore)) {
            $response_data['message'] = esc_html__('All selected files are already restored.', 'security-malware-firewall');
            wp_send_json_success($response_data);
        }

        $totally_failed_files_fast_hashes = $cure_log->getTotallyFailedFilesFastHashes();
        $totally_failed_files_fast_hashes = array_column($totally_failed_files_fast_hashes, 'fast_hash');
        $to_restore = array_diff($to_restore, $totally_failed_files_fast_hashes);

        $skipped = count($ids) - count($to_restore);

        foreach ($to_restore as $id) {
            $id = sanitize_user($id, true);
            $result = spbc_restore_file_from_backup_ajax_action_handler($id);
            if ($result['success'] !== true) {
                $path = spbc_scanner_get_file_by_id($id);
                $path = isset($path['path']) ? $path['path'] : 'unknown';
                $path = '..' . esc_html(substr($path, -50));
                $failed_to_restore[] = $path . ' - ' . !empty($result['error']) ? $result['error'] : __('Unknown error', 'security-malware-firewall');
            } else {
                $restored_on_request++;
            }
        }

        $response_data['restored_on_request'] = $restored_on_request;
        $response_data['skipped'] = $skipped;
        $response_data['failed_to_restore'] = $failed_to_restore;

        if (count($failed_to_restore) === 0) {
            wp_send_json_success($response_data);
        } else {
            $response_data['message'] = esc_html__('Some files were not restored.', 'security-malware-firewall');
            wp_send_json_error($response_data);
        }
    }
}
