<?php

namespace CleantalkSP\SpbctWP;

use CleantalkSP\SpbctWP\AdjustToEnvironmentModule\AdjustToEnvironmentHandler;
use CleantalkSP\SpbctWP\Scanner\ScannerAjaxEndpoints;
use CleantalkSP\SpbctWP\Scanner\ScanRepository;
use CleantalkSP\SpbctWP\Settings\FilesScanPathExclusion;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\PscCertifiedPluginsCache;

class Sync
{
    /**
     * @var array<string, string>
     */
    private const STEPS = array(
        'access_key_check'    => 'Checking access key...',
        'secfw_update'        => 'Start updating firewall...',
        'signatures_update'   => 'Update signatures...',
        'settings_exclusions' => 'Handling exclusions...',
        'adjust_env'          => 'Adjusting environment...',
        'vulnerability_check' => 'Running vulnerability check...',
        'analysis_log_update' => 'Updating cloud analysis verdicts...',
        'psc_cache_warm'      => 'Preparing Security Certified cache...',
        'finalize'            => 'Sync end...',
    );

    /**
     * @return array<string, mixed>
     */
    private static function getDefaultProgress()
    {
        return array(
            'in_progress'   => false,
            'step_index'    => 0,
            'account_is_ok' => false,
            'message'       => '',
            'percent'       => 0,
            'reload'        => false,
            'success'       => false,
            'last_step_at'  => 0,
        );
    }

    /**
     * @return array<string, mixed>
     */
    public static function getProgress()
    {
        global $spbc;

        $progress = isset($spbc->data['sync_progress']) && is_array($spbc->data['sync_progress'])
            ? $spbc->data['sync_progress']
            : array();

        return array_merge(self::getDefaultProgress(), $progress);
    }

    /**
     * @param array<string, mixed> $progress
     */
    private static function saveProgress($progress)
    {
        global $spbc;

        $spbc->data['sync_progress'] = $progress;
        $spbc->save('data');
    }

    /**
     * @param array<string, mixed> $progress
     *
     * @return array<string, mixed>
     */
    public static function formatStatus($progress)
    {
        return array(
            'error'       => false,
            'success'     => ! empty($progress['success']),
            'in_progress' => ! empty($progress['in_progress']),
            'message'     => (string) $progress['message'],
            'percent'     => (int) $progress['percent'],
            'reload'      => ! empty($progress['reload']),
        );
    }

    /**
     * Run full sync synchronously (RemoteCalls, WP-CLI).
     *
     * @return array{success: bool, reload: bool}
     */
    public static function run()
    {
        global $spbc;

        $progress = self::getProgress();
        if ( ! empty($progress['in_progress']) ) {
            self::saveProgress(self::getDefaultProgress());
        }

        $account_is_ok = false;

        self::stepAccessKeyCheck($account_is_ok);
        self::stepSecfwUpdate($account_is_ok);
        self::stepSignaturesUpdate();
        self::stepSettingsExclusions();
        self::stepAdjustEnv();
        self::stepVulnerabilityCheck();
        self::stepAnalysisLogUpdate($account_is_ok);
        self::stepPscCacheWarm();

        $out = array(
            'success' => true,
            'reload'  => self::needsReloadPage($spbc),
        );

        self::stepFinalize();

        return $out;
    }

    /**
     * Run the current sync step and advance progress.
     */
    public static function runCurrentStep()
    {
        global $spbc;

        $progress = self::getProgress();

        if ( empty($progress['in_progress']) ) {
            return;
        }

        $steps      = array_keys(self::STEPS);
        $step_index = (int) $progress['step_index'];

        if ( $step_index >= count($steps) ) {
            $progress['in_progress'] = false;
            $progress['success']     = true;
            $progress['percent']     = 100;
            self::saveProgress($progress);

            return;
        }

        $step_key = $steps[ $step_index ];

        $progress['message']      = self::STEPS[ $step_key ];
        $progress['percent']      = (int) round((($step_index + 1) / count($steps)) * 100);
        $progress['last_step_at'] = time();
        self::saveProgress($progress);

        $account_is_ok = (bool) $progress['account_is_ok'];

        switch ( $step_key ) {
            case 'access_key_check':
                self::stepAccessKeyCheck($account_is_ok);
                $should_check = $spbc->is_mainsite || $spbc->ms__work_mode != 2;
                if ( $should_check && ! $account_is_ok ) {
                    $progress                 = self::getProgress();
                    $progress['in_progress']  = false;
                    $progress['success']      = false;
                    $progress['reload']       = self::needsReloadPage($spbc);
                    $progress['percent']      = 100;
                    $progress['message']      = self::STEPS['access_key_check'];
                    $progress['last_step_at'] = time();
                    $spbc->data['key_changed'] = false;
                    self::saveProgress($progress);

                    return;
                }
                break;
            case 'secfw_update':
                self::stepSecfwUpdate($account_is_ok);
                break;
            case 'signatures_update':
                self::stepSignaturesUpdate();
                break;
            case 'settings_exclusions':
                self::stepSettingsExclusions();
                break;
            case 'adjust_env':
                self::stepAdjustEnv();
                break;
            case 'vulnerability_check':
                self::stepVulnerabilityCheck();
                break;
            case 'analysis_log_update':
                self::stepAnalysisLogUpdate($account_is_ok);
                break;
            case 'psc_cache_warm':
                self::stepPscCacheWarm();
                break;
            case 'finalize':
                self::stepFinalize();

                $progress                 = self::getProgress();
                $progress['in_progress']  = false;
                $progress['success']      = true;
                $progress['reload']       = self::needsReloadPage($spbc);
                $progress['percent']      = 100;
                $progress['message']      = self::STEPS['finalize'];
                $progress['last_step_at'] = time();
                self::saveProgress($progress);

                return;
        }

        $progress                  = self::getProgress();
        $progress['account_is_ok'] = $account_is_ok;
        $progress['step_index']    = $step_index + 1;
        $progress['last_step_at']  = time();
        self::saveProgress($progress);
    }

    private static function scheduleContinue()
    {
        $progress = self::getProgress();

        if ( empty($progress['in_progress']) ) {
            return;
        }

        $steps = array_keys(self::STEPS);
        if ( (int) $progress['step_index'] >= count($steps) ) {
            return;
        }

        RemoteCalls::performToHost('sync__continue', array(), array('async', 'get'));
    }

    /**
     * Resume stalled background sync if the last step was too long ago.
     */
    public static function maybeResume()
    {
        $progress = self::getProgress();

        if ( empty($progress['in_progress']) ) {
            return;
        }

        $steps = array_keys(self::STEPS);
        if ( (int) $progress['step_index'] >= count($steps) ) {
            return;
        }

        if ( time() - (int) $progress['last_step_at'] < 5 ) {
            return;
        }

        self::runCurrentStep();
        self::scheduleContinue();
    }

    /**
     * Start background sync. Safe to call when sync is already running — returns current status.
     *
     * @return array<string, mixed>
     */
    public static function startBackground()
    {
        $progress = self::getProgress();

        if ( ! empty($progress['in_progress']) ) {
            self::maybeResume();

            return self::formatStatus(self::getProgress());
        }

        $steps  = array_keys(self::STEPS);
        $labels = self::STEPS;

        $progress = array(
            'in_progress'   => true,
            'step_index'    => 0,
            'account_is_ok' => false,
            'message'       => $labels[ $steps[0] ],
            'percent'       => 0,
            'reload'        => false,
            'success'       => false,
            'last_step_at'  => time(),
        );
        self::saveProgress($progress);

        self::runCurrentStep();

        $progress = self::getProgress();
        if ( ! empty($progress['in_progress']) ) {
            self::scheduleContinue();
        }

        return self::formatStatus(self::getProgress());
    }

    /**
     * Remote call handler: run the next sync step.
     */
    public static function continueSync()
    {
        self::runCurrentStep();

        $progress = self::getProgress();
        if ( ! empty($progress['in_progress']) ) {
            self::scheduleContinue();
        }
    }

    private static function stepAccessKeyCheck(&$account_is_ok)
    {
        global $spbc;

        $spbc->error_delete_all(true);

        $account_is_ok = false;

        if ( $spbc->is_mainsite || $spbc->ms__work_mode != 2 ) {
            $account_is_ok = ( spbc_check_account_status($spbc->api_key) === true );
        }
    }

    private static function stepSecfwUpdate($account_is_ok)
    {
        global $spbc;

        if ( ! $account_is_ok ) {
            return;
        }

        $result = spbc_send_logs($spbc->api_key);
        if ( empty($result['error']) ) {
            $spbc->data['logs_last_sent']         = current_time('timestamp');
            $spbc->data['last_sent_events_count'] = $result;
            $spbc->error_delete('send_logs');
        } else {
            $spbc->error_add('send_logs', $result);
        }

        $result = spbc_send_firewall_logs($spbc->api_key);
        if ( empty($result['error']) ) {
            $spbc->fw_stats['last_send']       = current_time('timestamp');
            $spbc->fw_stats['last_send_count'] = $result;
            $spbc->error_delete('send_firewall_logs');
        } else {
            $spbc->error_add('send_firewall_logs', $result);
        }

        $result_service_get = spbct_perform_service_get();
        if ( ! empty($result_service_get['error']) && $result_service_get['error_no'] !== 403 ) {
            $spbc->error_add('service_customize', $result_service_get['error']);
        }

        $spbc->remote_calls['update_security_firewall']['last_call'] = 0;
        $spbc->save('remote_calls', true, false);

        $result = spbc_security_firewall_update__init();
        if ( ! empty($result['error']) ) {
            $spbc->error_add('firewall_update', $result['error']);
        }

        $spbc->save('data');
        $spbc->save('fw_stats', true, false);
    }

    private static function stepSignaturesUpdate()
    {
        global $spbc;

        if ( ! is_main_site() ) {
            return;
        }

        $result = spbc_scanner__signatures_update();
        empty($result['error'])
            ? $spbc->error_delete('scanner_update_signatures', 'save')
            : $spbc->error_add('scanner_update_signatures', $result);
    }

    private static function stepSettingsExclusions()
    {
        global $spbc;

        $result_update_exclusions = spbc_update_scan_settings_exclusions();

        Cron::updateTask(
            'update_scan_settings_exclusions',
            'spbc_update_scan_settings_exclusions',
            FilesScanPathExclusion::EXTERNAL_SOURCE_UPDATE_PERIOD
        );

        if ( is_array($result_update_exclusions) && ! empty($result_update_exclusions['error']) ) {
            $spbc->error_add('update_exclusions', $result_update_exclusions['error']);
        }
    }

    private static function stepAdjustEnv()
    {
        $adjust = new AdjustToEnvironmentHandler();
        $adjust->handle();
    }

    private static function stepVulnerabilityCheck()
    {
        Cron::updateTask('check_vulnerabilities', 'spbc_security_check_vulnerabilities', 86400, time());
    }

    /**
     * Pull actual cloud verdicts for the scan results files.
     * The cloud verdict may be changed after the file processing is done,
     * so the files with the final verdict have to be re-checked too.
     *
     * @param bool $account_is_ok
     */
    private static function stepAnalysisLogUpdate($account_is_ok)
    {
        global $spbc;

        if ( ! $account_is_ok || ! is_main_site() ) {
            return;
        }

        $file_ids = ScanRepository::getCloudAnalysisFilesFastHashes();

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

        $result = ScannerAjaxEndpoints::checkFilesAnalysisStatus(true, $file_ids);

        if ( ! empty($result['error']) ) {
            $spbc->error_add('analysis_log_update', $result);
        } else {
            $spbc->error_delete('analysis_log_update');
        }
    }

    private static function stepPscCacheWarm()
    {
        try {
            PscCertifiedPluginsCache::warm();
        } catch ( \Exception $exception ) {
            spbc_log('Couldn\'t warm the PSC certified plugins cache. ' . $exception->getMessage());
        }
    }

    private static function stepFinalize()
    {
        global $spbc;

        $spbc->data['key_changed']    = false;
        $spbc->data['last_sync_date'] = time();
        $spbc->save('data');
        $spbc->save('fw_stats', true, false);
    }

    /**
     * @param State $spbc
     * @return bool
     */
    private static function needsReloadPage($spbc)
    {
        $key_changed = false;
        $has_errors = false;
        if ($spbc instanceof State) {
            $key_changed = (bool)$spbc->data['key_changed'];
            if ($spbc->errors instanceof \Countable || is_array($spbc->errors)) {
                $has_errors = count($spbc->errors) > 0;
            }
        }

        return $has_errors || $key_changed;
    }
}
