<?php

namespace ReallySimplePlugins\RSS\Core\Features\Onboarding;

use ReallySimplePlugins\RSS\Core\Bootstrap\App;
use ReallySimplePlugins\RSS\Core\Interfaces\DoActionInterface;
use ReallySimplePlugins\RSS\Core\Interfaces\FeatureInterface;
use ReallySimplePlugins\RSS\Core\Services\CertificateService;
use ReallySimplePlugins\RSS\Core\Services\EmailService;
use ReallySimplePlugins\RSS\Core\Services\RelatedPluginService;
use ReallySimplePlugins\RSS\Core\Services\SecureSocketsService;
use ReallySimplePlugins\RSS\Core\Services\SettingsConfigService;
use ReallySimplePlugins\RSS\Core\Support\Helpers\Storage;
use ReallySimplePlugins\RSS\Core\Support\Helpers\Storages\EnvironmentConfig;
use ReallySimplePlugins\RSS\Core\Support\Utility\StringUtility;
use ReallySimplePlugins\RSS\Core\Traits\HasNonces;

class OnboardingController implements FeatureInterface, DoActionInterface
{
    use HasNonces;

    private EmailService $emailService;
    private OnboardingFeatureService $service;
    private SecureSocketsService $sslService;
    private RelatedPluginService $pluginService;
    private SettingsConfigService $settingsService;
    private CertificateService $certificateService;
    private EnvironmentConfig $env;

    public function __construct(
        OnboardingFeatureService $service,
        SecureSocketsService $sslService,
        EmailService $emailService,
        RelatedPluginService $pluginService,
        SettingsConfigService $settingsService,
        CertificateService $certificateService,
        EnvironmentConfig $environmentConfig
    ) {
        $this->env = $environmentConfig;
        $this->service = $service;
        $this->sslService = $sslService;
        $this->emailService = $emailService;
        $this->pluginService = $pluginService;
        $this->settingsService = $settingsService;
        $this->certificateService = $certificateService;
    }

    public function register(): void
    {
        add_filter('rsssl_run_test', [$this, 'processOnboardingTest'], 10, 3);
        add_filter('rsssl_do_action', [$this, 'rssslDoAction'], 10, 3);
        add_action($this->env->getString('onboarding.queue_event'), [$this, 'processQueuedEvent']);
    }

    /**
     * Method processes the onboarding request for SSL activation. The responses
     * are validated by the {@see rsssl_run_test} filter.
     * @return array|bool
     */
    public function processOnboardingTest(array $response, string $action, array $data)
    {
        switch ($action) {
            case 'activate_ssl':
                $data['is_rest_request'] = true;
                $response = $this->sslService->activateSSL($data);
                break;
            case 'activate_ssl_networkwide':
                $response = $this->service->processMultisiteActivationStep();
                break;
            default:
                return $response;
        }

        return $response;
    }

    /**
     * Method to dynamically parse onboarding actions. The action is parsed to
     * PascalCase and if it is an onboarding action a dedicated method is
     * called based on the format: process{OnboardingAction}Action. These
     * methods all have access to the Storage object which contains all data
     * of the request. Each method cán use it, but its not mandatory of course.
     *
     * @uses processOnboardingDataAction, processGetModalStatusAction
     * @uses processDismissModalAction, processOverrideSslDetectionAction
     * @uses processUpdateEmailAction, processActivateAction
     * @uses processDownloadAction
     */
    public function rssslDoAction(array $response, string $action, $data): array
    {
        $actionableMethod = 'process' . StringUtility::snakeToPascalCase($action) . 'Action';

        // Current action is not one we want to process
        if (method_exists($this, $actionableMethod) === false) {
            return $response;
        }

        // Method exists. Try to execute and return the response.
        try {
            $storage = new Storage($data);
            return $this->$actionableMethod($storage);
        } catch (\Exception $exception) {
            return array_merge($response, [
                'success' => false,
                'message' => $exception->getMessage(),
            ]);
        }
    }

    /**
     * Resets onboarding state when user clicks the "Activate SSL" button
     * after dismissal.
     *
     * When a user dismisses the onboarding modal and later clicks the
     * "Activate SSL" button, we need to reset the onboarding state options
     * (rsssl_show_onboarding and rsssl_onboarding_dismissed) to allow the
     * onboarding flow to proceed.
     *
     * The OnboardingLoader has a bypass that loads the Controller when it
     * detects the activateSSLClicked flag in the request body. Once loaded,
     * we reset the options here at the start of processOnboardingDataAction
     * so the onboarding state is consistent for the rest of the request and
     * future requests.
     *
     * We can't split this into two separate actions (one to reset, one to
     * fetch data) because a separate reset action without the
     * activateSSLClicked flag would be blocked by the Loader checking the
     * dismissed state.
     *
     * @return void
     */
    private function onActivateSslClick(): void
    {
        $this->service->resetOnboarding();
    }

    /**
     * Two possibilities:
     * - a new install: show activation notice, and process onboarding
     * - an upgrade to 6. Only show new features.
     * @internal action: onboarding_data
     * @throws \RuntimeException
     */
    protected function processOnboardingDataAction(Storage $data): array
    {
        $nonce = $data->getString('nonce');
        if ($this->verifyNonce($nonce, 'rsssl_nonce') === false) {
            throw new \RuntimeException(esc_html__('Nonce validation failed', 'really-simple-ssl'));
        }

        // Reset onboarding state if user clicked "Activate SSL" after dismissal
        if ($data->getBoolean('activateSSLClicked')) {
            $this->onActivateSslClick();
        }

        // For an upgrade from free, we should check the rsssl_free_deactivated
        // option. When upgrading from Pro from Free, rsssl_deactivate_alternate
        // is called in the Free plugin. Therefore, we have to check this option.
        // This is not something we can easily change, because the free plugin has
        // to be updated before we can check this in Pro.
        $isUpgradeFromFree = get_option('rsssl_free_deactivated');
        delete_option('rsssl_free_deactivated');

        $stepsGenerator = App::getInstance()->make(OnboardingStepsGenerator::class);
        $onboardingSteps = $stepsGenerator->generate($isUpgradeFromFree);

        //if the user called with a refresh action, clear the cache
        if ($data->getBoolean('forceRefresh')) {
            delete_transient('rsssl_certinfo');
        }

        return [
            'steps' => $onboardingSteps,
            'ssl_enabled' => rsssl_get_option('ssl_enabled'),
            'ssl_detection_overridden' => get_option('rsssl_ssl_detection_overridden'),
            'certificate_valid' => $this->certificateService->isValid(),
            'networkwide' => (is_multisite() && rsssl_is_networkwide_active()),
            'network_activation_status' => get_site_option('rsssl_network_activation_status'),
            'rsssl_upgraded_from_free' => $isUpgradeFromFree,
        ];
    }

    /**
     * Method determines if the onboarding modal should be shown
     * @internal action: get_modal_status
     */
    protected function processGetModalStatusAction(Storage $data): array
    {
        return [
            'dismissed' => ($this->service->showOnboardingModal() === false),
        ];
    }

    /**
     * Method processes the user action to dismiss the onboarding modal. It will
     * trigger the event to process any queued items immediately.
     * @internal action: dismiss_modal
     */
    protected function processDismissModalAction(Storage $data): array
    {
        $updated = update_option('rsssl_onboarding_dismissed', $data->getBoolean('dismiss'), false);

        if (!empty($this->service->getQueuedItems())) {
            $this->service->manuallyProcessQueueNow();
        }

        return [
            'success' => $updated,
        ];
    }

    /**
     * Update SSL detection overridden option
     * @internal action: override_ssl_detection
     */
    protected function processOverrideSslDetectionAction(Storage $data): array
    {
        if ($data->getBoolean('overrideSSL')) {
            $success = update_option('rsssl_ssl_detection_overridden', true, false);
        } else {
            $success = delete_option('rsssl_ssl_detection_overridden');
        }

        return [
            'success' => $success
        ];
    }

    /**
     * Method processes the given email, if it is valid we send a verification
     * mail. If the user choose to receive tips&tricks then we add them to
     * our mailing list as well.
     * @internal action: update_email
     */
    protected function processUpdateEmailAction(Storage $data): array
    {
        $email = $data->getEmail('email');

        // Abort.
        if (is_email($email) === false) {
            return [
                'success' => false,
            ];
        }

        rsssl_update_option('send_notifications_email', 1);

        if ($data->getBoolean('includeTips')) {
            $this->emailService->addEmailToMailingList($email);
        }

        $this->emailService->setEmail($email);
        return $this->emailService->sendVerificationMail();
    }

    /**
     * Method processes the download action for a related plugin. It does not
     * immediately download this related plugin, but it adds it to a queue that
     * is processed on the next page load. This prevents users breaking the
     * process by refreshing the page (for example)
     * @internal action: download
     */
    protected function processDownloadAction(Storage $data): array
    {
        $this->service->queueOnboardingItem(
            $data->getTitle('id'),
            'download'
        );

        return [
            'next_action' => 'activate',
            'success' => true,
        ];
    }

    /**
     * Method processes the activation action for a related plugin. It does not
     * immediately activate this related plugin, but it adds it to a queue that
     * is processed on the next page load. This prevents users breaking the
     * process by refreshing the page (for example)
     * @internal action: activate
     */
    protected function processActivateAction(Storage $data): array
    {
        $this->service->queueOnboardingItem(
            $data->getTitle('id'),
            'activate'
        );

        return [
            'next_action' => 'completed',
            'success' => true,
        ];
    }

    /**
     * Process the plugins to download/activate queue
     */
    public function processQueuedEvent(): void
    {
        $queuedItems = $this->service->getQueuedItems();

        foreach ($queuedItems as $key => &$item) {
            if (!isset($item['status'], $item['action']) || $item['status'] !== 'pending') {
                continue;
            }

            // Mark as processing
            $item['status'] = 'processing';
            $this->service->updateQueuedItems($queuedItems);

            $this->pluginService->setPluginConfigBySlug($item['item_id']);
            $success = $this->pluginService->executeAction(
                sanitize_text_field($item['action'])
            );

            // Update status
            $item['status'] = $success ? 'completed' : 'failed';
            $item['completed'] = time();
        }

        $this->service->updateQueuedItems($queuedItems);
        $this->service->cleanupQueuedItems();
    }
}
