<?php

namespace CleantalkSP\SpbctWP\Scanner\Services;

use CleantalkSP\SpbctWP\API as SpbcAPI;
use CleantalkSP\SpbctWP\DTO\MScanFilesDTO;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\ScanActions;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\ScanResultsTableActions;
use CleantalkSP\SpbctWP\Scanner\ScanRepository;
use CleantalkSP\SpbctWP\Scanner\ScanStorage;

class SendFileToCloudService
{
    /**
     * Send file to Cleantalk Cloud
     * @param string $file_id
     * @param bool $do_rescan
     * @return array
     * @psalm-suppress PossiblyUnusedMethod
     */
    public static function sendFile($file_id, $do_rescan = true)
    {
        global $spbc, $wpdb;

        if (!$file_id) {
            return array('error' => 'WRONG_FILE_ID');
        }

        $file_info = ScanRepository::getFileInfoByFastHash($file_id);
        if (empty($file_info)) {
            return array('error' => 'FILE_NOT_FOUND');
        }

        $root_path = spbc_get_root_path();

        // if file not exists, remove it from the database
        if (!file_exists($root_path . $file_info['path'])) {
            $res = ScanResultsTableActions::removeFileFromTable($file_id);
            if ($res === false) {
                return array(
                    'error' => __('File not exists and must be removed from log, but something went wrong.', 'security-malware-firewall'),
                    'error_type' => 'FILE_NOT_EXISTS_DB_ERROR'
                );
            }

            return array(
                'error' => __('File not exists and will be removed from log.', 'security-malware-firewall'),
                'error_type' => 'FILE_NOT_EXISTS'
            );
        }

        if (!is_readable($root_path . $file_info['path'])) {
            return array('error' => 'FILE_NOT_READABLE');
        }

        if (filesize($root_path . $file_info['path']) < 1) {
            return array('error' => 'FILE_SIZE_ZERO');
        }

        if (filesize($root_path . $file_info['path']) > 1048570) {
            return array('error' => 'FILE_SIZE_TOO_LARGE');
        }

        if ($file_info['status'] === 'APPROVED_BY_CT' || $file_info['status'] === 'APPROVED_BY_CLOUD') {
            return array('error' => 'IT_IS_IMPOSIBLE_RESEND_APPROVED_FILE');
        }

        // @todo: check if $do_rescan is needed, because it's used for every file now.
        if ( $do_rescan ) {
            // Scan file before send it
            $rescan_results = ScanActions::rescanSingleFile($file_info['path'], $file_info['full_hash'], $root_path);
            if (isset($rescan_results['error'])) {
                return array('error' => $rescan_results['error']);
            }

            $merged_result = $rescan_results['merged_result'];

            // prepare weakspots for DTO
            $file_info['weak_spots'] = $merged_result['weak_spots'];

            // update file in the table
            $wpdb->update(
                SPBC_TBL_SCAN_FILES,
                array(
                    'checked_signatures' => $file_info['checked_signatures'],
                    'checked_heuristic'  => $file_info['checked_heuristic'],
                    'status'             => $file_info['status'] === 'MODIFIED' ? 'MODIFIED' : $merged_result['status'],
                    'severity'           => $merged_result['severity'],
                    'weak_spots'         => json_encode($merged_result['weak_spots']),
                    'full_hash'          => md5_file($root_path . $file_info['path']),
                ),
                array('fast_hash' => $file_info['fast_hash']),
                array('%s', '%s', '%s', '%s', '%s', '%s'),
                array('%s')
            );
        }

        // Updating file_info if file source is unknown
        if ( ! isset($file_info['version'], $file_info['source'], $file_info['source_type'])) {
            $file_info_updated = spbc_get_source_info_of($file_info['path']);
            if ($file_info_updated) {
                $file_info = array_merge($file_info, $file_info_updated);
            }
        }

        // prepare file hash
        $file_info['full_hash'] = md5_file($root_path . $file_info['path']);

        // Getting file && API call
        $file_content = file_get_contents($root_path . $file_info['path']);
        try {
            $dto = new MScanFilesDTO(
                array(
                    'path_to_sfile' => $file_info['path'],
                    'attached_sfile' => $file_content,
                    'md5sum_sfile' => $file_info['full_hash'],
                    'dangerous_code' => $file_info['weak_spots'],
                    'version' => $file_info['version'],
                    'source' => $file_info['source'],
                    'source_type' => $file_info['source_type'],
                    'source_status' => $file_info['source_status'],
                    'of_plugin_dir_success' => $file_info['of_plugin_dir_success'] === 'NULL'
                        ? -1
                        : $file_info['of_plugin_dir_success'],
                    'of_plugin_dir_scan_info' => $file_info['of_plugin_dir_scan_info'],
                    'real_hash' => $file_info['real_full_hash'],
                    'client_php_version' => phpversion(),
                    'auto_send_type' => 'Suspicious',
                    'current_scanner_settings' => json_encode($spbc->settings),
                    'plugin_heuristic_checked' => $file_info['checked_heuristic'],
                    'plugin_signatures_checked' => $file_info['checked_signatures'],
                )
            );
        } catch ( \InvalidArgumentException $e ) {
            return array('error' => "File can not be send. Error: \n" . substr($e->getMessage(), 0, 100));
        }

        $api_response = SpbcAPI::method__security_pscan_files_send($spbc->settings['spbc_key'], $dto);

        if (!empty($api_response['error'])) {
            return self::handleApiError($api_response, $file_id);
        }

        if (!isset($api_response['file_id'])) {
            return array('error' => 'API_RESPONSE: file_id is NULL');
        }

        $sql_result = ScanStorage::updateSendFileStatusAsSuccess($file_id, $api_response['file_id']);

        if ($sql_result === false) {
            return array('error' => 'DB_COULDNT_UPDATE pscan_processing_status');
        }

        self::addCronToUpdateStatuses();

        return array('success' => true, 'result' => $api_response);
    }

    protected static function handleApiError($api_response, $file_id)
    {
        if ($api_response['error'] === 'QUEUE_FULL') {
            $sql_result = ScanStorage::setFileAsPendingQueue($file_id);

            if ($sql_result === false) {
                return array('error' => 'DB_COULD_NOT_UPDATE pscan_pending_queue');
            }

            self::addCronToResendUnqueuedFiles();

            return array('success' => true, 'result' => $api_response);
        } else {
            // out API error if error is not queue_full
            return $api_response;
        }
    }

    private static function addCronToUpdateStatuses()
    {
        \CleantalkSP\SpbctWP\Cron::updateTask(
            'scanner_update_pscan_files_status',
            'spbc_scanner_update_pscan_files_status',
            SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD,
            time() + SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD
        );
    }

    private static function addCronToResendUnqueuedFiles()
    {
        \CleantalkSP\SpbctWP\Cron::updateTask(
            'scanner_resend_pscan_files',
            'spbc_scanner_resend_pscan_files',
            SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD,
            time() + SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD
        );
    }
}
