<?php

namespace CleantalkSP\SpbctWP\Scanner\ScannerActions;

use CleantalkSP\SpbctWP\Scanner\Services\SendFileToCloudService;
use CleantalkSP\SpbctWP\API as SpbcAPI;

class CloudAnalysisActions
{
    /**
     * tested
     * @param array $response API Response
     * @param bool $await_estimated_data Do await estimated data set on undone files check
     * @return mixed API Response
     * @throws \Exception if validation failed
     */
    public static function validateAnalysisStatusResponse($response)
    {
        // Check if API error
        if ( !empty($response['error']) ) {
            throw new \Exception('API error: "' . $response['error'] . "\"");
        }

        // Check if processing_status is set and correct
        if ( !isset($response['processing_status']) ) {
            throw new \Exception('response provided no processing status');
        }

        // Set allowed statuses
        $allowed_statuses_array = array(
            'NEW',
            'DONE',
            'ERROR',
            'IN_SCANER',
            'NEW_CLOUD',
            'IN_CLOUD',
            'IN_SANDBOX',
            'NEW_SANDBOX',
            'UNKNOWN'
        );

        // Check allowed statuses
        if ( !in_array($response['processing_status'], $allowed_statuses_array) ) {
            throw new \Exception('response provided unknown processing status "' . $response['processing_status'] . "\"");
        }

        // Check precessing status
        if ( $response['processing_status'] === 'DONE' && !isset($response['file_status']) ) {
            throw new \Exception('process finished, but status is unset');
        }

        if ( $response['processing_status'] === 'DONE' ) {
            // Check file_status
            if ( !in_array($response['file_status'], array('DANGEROUS', 'SAFE')) ) {
                throw new \Exception('process finished, but status is unknown: "' . $response['file_status'] . "\"");
            }
        }

        //estimated time validation
        if ( $response['processing_status'] !== 'DONE' ) {
            if ( ! isset($response['estimated_execution_time'])) {
                throw new \Exception('response provided no estimated scan time');
            }
            //todo remove on business decision
            //if ( ! isset($response['number_of_files'])) {
            //  throw new Exception('response provided no number of estimated files');
            //}
            //if ( ! isset($response['number_of_files_scanned'])) {
            //  throw new Exception('response provided no number of already scanned files');
            //}
        }

        return $response;
    }

    /**
     * tested
     * Check if status updater should skip this file
     * @param $file_info
     * @return array array of file info
     * @throws \Exception message contains reason for skip the file
     */
    public static function isStatusUpdaterExcludedFile(array $file_info)
    {
        //skip quarantined files
        if (isset($file_info['status']) && $file_info['status'] === 'QUARANTINED') {
            throw new \Exception('skipped');
        }

        // skip not queued files
        $pscan_pending_queue = isset($file_info['pscan_pending_queue']) && $file_info['pscan_pending_queue'] == '1';
        if ($pscan_pending_queue) {
            throw new \Exception('queued');
        }

        //skip maual analysis checked
        if (empty($file_info['pscan_processing_status'])) {
            throw new \Exception('skipped');
        }

        return $file_info;
    }

    //------------ ANALYSIS ------------------
    /**
     * tested
     * Validate file info collected for pscan status updating process
     * @param $file_info array|false array of file info or false if we could not collect this
     * @return array origin array of file info
     * @throws \Exception
     */
    public static function validateFileInfoBeforeSendToAnalysis($file_info)
    {
        // SQL query result validation
        if ( $file_info !== false ) {
            // Validate path
            $file_path = !empty($file_info['path']) ? $file_info['path'] : false;
            if (!$file_path) {
                throw new \Exception('can not get file path');
            }

            // Set pscan file id to be sure that file is correctly reached from db
            $pscan_file_id = !empty($file_info['pscan_file_id']) ? $file_info['pscan_file_id'] : false;
            if ( !$pscan_file_id ) {
                throw new \Exception('can not get pscan_file_id');
            }
        } else {
            throw new \Exception('can not get file info');
        }
        return $file_info;
    }

    /**
     * Send files for analysis in bulk
     * @param array $fast_hashes_list
     * @return array
     */
    public static function bulkSendFileForAnalysis($fast_hashes_list = array())
    {
        global $wpdb;

        if ( ! $fast_hashes_list) {
            return array('error' => 'Record has no file id to send.');
        }

        $sql_result = $wpdb->get_results(
            'SELECT fast_hash FROM ' . SPBC_TBL_SCAN_FILES . '
        WHERE last_sent IS NULL
        AND status NOT IN ("APPROVED_BY_USER","APPROVED_BY_CT","APPROVED_BY_CLOUD","DENIED_BY_CT")
        AND (source IS NULL OR source != \'BINARY\')',
            ARRAY_A
        );

        $sql_result = array_map(
            function ($data) {
                return $data['fast_hash'];
            },
            $sql_result
        );

        $not_sent_files_intersection = array_values(array_intersect($fast_hashes_list, $sql_result));

        $out = array('files_sent_counter' => 0);

        if ( ! empty($not_sent_files_intersection)) {
            foreach ($not_sent_files_intersection as $fast_hash) {
                $result = SendFileToCloudService::sendFile($fast_hash);

                if ( ! empty($result['error'])) {
                    $file_info             = ScanResultsTableActions::getFileByID($fast_hash);
                    $file_path             = isset($file_info['path']) ? $file_info['path'] : 'UNKNOWN_FILE';
                    $out['error']          = __('Some files were not updated.', 'security-malware-firewall');
                    $out['error_detail'][] = array(
                        'file_path' => $file_path,
                        'error'     => $result['error'],
                    );
                }

                $out['files_sent_counter']++;
            }
        } else {
            $out['error'] = __('All the available files have been already sent.', 'security-malware-firewall');
        }

        return $out;
    }

    /**
     * Check files analysis status
     * @param string|array $file_ids_input
     * @return array
     */
    public static function checkFilesAnalysisStatus($file_ids_input = '')
    {
        global $spbc, $wpdb;

        // Parse if there are more than 1 file
        $_file_ids_input = is_string($file_ids_input) ? explode(',', $file_ids_input) : $file_ids_input;

        // Initialize output array
        $out = array();

        // Prepare counters
        $counters = array(
            'queued' => 0,
            'updated' => 0,
            'failed' => 0,
            'skipped' => 0,
            'total' => count($_file_ids_input)
        );

        foreach ( $_file_ids_input as $file_id ) {
            $file_info = ScanResultsTableActions::getFileByID($file_id);

            // Validate file info
            try {
                $file_info = CloudAnalysisActions::validateFileInfoBeforeSendToAnalysis($file_info);
            } catch (\Exception $e) {
                $out['error_detail'][] = array(
                    'error' => $e->getMessage(),
                );
                $counters['failed']++;
                continue;
            }

            // Check if file is excluded
            try {
                $file_info = CloudAnalysisActions::isStatusUpdaterExcludedFile($file_info);
            } catch (\Exception $e) {
                switch ($e->getMessage()) {
                    case 'skipped':
                        $counters['skipped']++;
                        break;
                    case 'queued':
                        $counters['queued']++;
                        break;
                }
                continue;
            }

            // Perform API call
            $api_response = SpbcAPI::method__security_pscan_status($spbc->settings['spbc_key'], $file_info['pscan_file_id']);

            // Validate API response
            try {
                $api_response = CloudAnalysisActions::validateAnalysisStatusResponse($api_response);
            } catch ( \Exception $exception ) {
                $out['error_detail'][] = array(
                    'file_path' => $file_info['path'],
                    'pscan_file_id' => $file_info['pscan_file_id'],
                    'fast_hash' => $file_id,
                    'error' => 'API response validation failed "' . $exception->getMessage() . "\"",
                );
                continue;
            }

            $update_query = self::filesAnalysisStatusUpdateQuery($api_response, $file_info);
            $update_result = $wpdb->query($update_query);

            if ( $update_result === false ) {
                // Collect errors
                $out['error_detail'][] = array(
                    'file_path' => $file_info['path'],
                    'pscan_file_id' => $file_info['pscan_file_id'],
                    'fast_hash' => $file_id,
                    'error' => 'COULDNT_UPDATE file status',
                );
            } else {
                // All is fine, inc updated counter
                $counters['updated']++;
            }
        }

        // Process errors
        if ( !empty($out['error_detail']) ) {
            $out['error'] = __('Some files were not updated.', 'security-malware-firewall');
        }

        // Fill counters
        $out['counters'] = $counters;

        return $out;
    }

    /**
     * Prepare query for files analysis status update
     * @param array $api_response
     * @param array $file_info
     * @return string
     */
    private static function filesAnalysisStatusUpdateQuery($api_response, $file_info)
    {
        global $wpdb;

        if ( $api_response['processing_status'] !== 'DONE' ) {
            return $wpdb->prepare(
                'UPDATE ' . SPBC_TBL_SCAN_FILES
                . ' SET pscan_pending_queue = 0, pscan_processing_status  = %s, pscan_estimated_execution_time  = %s'
                . ' WHERE pscan_file_id = %s',
                $api_response['processing_status'],
                $api_response['estimated_execution_time'],
                $file_info['pscan_file_id']
            );
        }

        if ( $api_response['file_status'] === 'SAFE' ) {
            return $wpdb->prepare(
                'UPDATE ' . SPBC_TBL_SCAN_FILES
                . ' SET '
                . ' pscan_processing_status  = "DONE",'
                . ' pscan_pending_queue = 0, '
                . ' pscan_status  = "SAFE",'
                . ' pscan_balls  = %s,'
                . ' status = "APPROVED_BY_CLOUD",'
                . ' pscan_estimated_execution_time = NULL'
                . ' WHERE pscan_file_id = %s',
                isset($api_response['file_balls']) ? $api_response['file_balls'] : '{SAFE:0}',
                $file_info['pscan_file_id']
            );
        }

        return $wpdb->prepare(
            'UPDATE ' . SPBC_TBL_SCAN_FILES
            . ' SET '
            . ' pscan_processing_status  = "DONE",'
            . ' pscan_pending_queue = 0, '
            . ' pscan_status  = %s ,'
            . ' severity  = "CRITICAL",'
            . ' pscan_balls  = %s,'
            . ' status  = "DENIED_BY_CLOUD",'
            . ' pscan_estimated_execution_time = NULL'
            . ' WHERE pscan_file_id = %s',
            $api_response['file_status'],
            isset($api_response['file_balls']) ? $api_response['file_balls'] : '{DANGEROUS:0}',
            $file_info['pscan_file_id']
        );
    }
}
