<?php

namespace SimplyBook\Controllers;

use Carbon\Carbon;
use SimplyBook\Http\ApiClient;
use SimplyBook\Services\PluginFirstUseTimeService;
use SimplyBook\Traits\HasViews;
use SimplyBook\Traits\HasAllowlistControl;
use SimplyBook\Interfaces\ControllerInterface;
use SimplyBook\Services\NoticeDismissalService;
use SimplyBook\Support\Helpers\Storages\RequestStorage;
use SimplyBook\Support\Helpers\Storages\EnvironmentConfig;

class ReviewController implements ControllerInterface
{
    use HasViews;
    use HasAllowlistControl;

    private string $reviewAction = 'rsp_review_form_submit';
    private string $reviewNonceName = 'rsp_review_nonce';
    private int $bookingThreshold = 2;
    private int $bookingsAmount; // Used as object cache

    private ApiClient $client;
    private PluginFirstUseTimeService $pluginFirstUseTimeService;
    private EnvironmentConfig $env;
    private RequestStorage $request;
    private NoticeDismissalService $noticeDismissalService;

    public function __construct(ApiClient $client, PluginFirstUseTimeService $pluginFirstUseTimeService, EnvironmentConfig $env, RequestStorage $request, NoticeDismissalService $noticeDismissalService)
    {
        $this->client = $client;
        $this->pluginFirstUseTimeService = $pluginFirstUseTimeService;
        $this->env = $env;
        $this->request = $request;
        $this->noticeDismissalService = $noticeDismissalService;
    }

    public function register(): void
    {
        if ($this->adminAccessAllowed() === false) {
            return;
        }

        add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']);
        add_action('admin_notices', [$this, 'showLeaveReviewNotice']);
        add_action('admin_init', [$this, 'processReviewFormSubmit']);
    }

    /**
     * Show a notice to leave a review
     */
    public function showLeaveReviewNotice(): void
    {
        if ($this->canRenderReviewNotice() === false) {
            return;
        }

        $reviewMessage = sprintf(
            // translators: %1$d is replaced by the amount of bookings, %2$ and %23$ are replaced with opening and closing a tag containing hyperlink
            __('Hi, SimplyBook.me has helped you reach %1$d bookings in the last 30 days. If you have a moment, please consider leaving a review on WordPress.org to spread the word. We greatly appreciate it! If you have any questions or feedback, leave us a %2$smessage%3$s.', 'simplybook'),
            $this->getAmountOfBookings(),
            '<a href="' . $this->env->getUrl('simplybook.support_url') . '"  rel="noopener noreferrer"  target="_blank">',
            '</a>'
        );

        $this->render('admin/review-notice', [
            'logoUrl' => $this->env->getUrl('plugin.assets_url') . 'img/simplybook-S-logo.png',
            'reviewUrl' => $this->env->getUrl('simplybook.review_url'),
            'reviewMessage' => $reviewMessage,
            'reviewAction' => $this->reviewAction,
            'reviewNonceName' => $this->reviewNonceName,
        ]);
    }

    /**
     * Process the review form submit
     */
    public function processReviewFormSubmit(): void
    {
        if ($this->request->isEmpty('global.rsp_review_form')) {
            return;
        }

        $nonce = $this->request->get('global.' . $this->reviewNonceName);
        if (wp_verify_nonce($nonce, $this->reviewAction) === false) {
            return; // Invalid nonce
        }

        $choice = $this->request->getString('global.rsp_review_choice');
        if ($choice === 'later') {
            update_option('simplybook_review_notice_dismissed_time', time(), false);
            update_option('simplybook_review_notice_choice', 'later', false);
        }

        if ($choice === 'never') {
            update_option('simplybook_review_notice_choice', 'never', false);
        }
    }

    /**
     * Check if the review notice can be rendered. True when:
     * - The user still has an authenticated SimplyBook session
     * - The user has not dismissed the notice
     * - The plugin first-use time is suitable for review
     * - The review notice dismissed time has passed
     * - The amount of bookings is greater than the threshold
     * - The user is not on an edit screen
     */
    private function canRenderReviewNotice(): bool
    {
        if ($this->client->isAuthenticated() === false) {
            return false;
        }

        // Check if user dismissed via X button
        if ($this->noticeDismissalService->isNoticeDismissed(get_current_user_id(), 'review')) {
            return false;
        }

        // Check if user dismissed via form button
        $previousChoice = get_option('simplybook_review_notice_choice');
        if ($previousChoice === 'never') {
            return false;
        }

        if ($this->pluginFirstUseTimeSuitableForReview() === false) {
            return false;
        }

        if ($this->reviewNoticeDismissedTimeHasPassed() === false) {
            return false;
        }

        if ($this->getAmountOfBookings() < $this->bookingThreshold) {
            return false;
        }

        // Prevent showing the review on edit screen, as gutenberg removes the
        // class which makes it editable.
        $screen = get_current_screen();
        if ($screen && ('post' === $screen->base)) {
            return false;
        }

        return true;
    }

    /**
     * Check if the plugin first-use time is more than 30 days ago.
     */
    private function pluginFirstUseTimeSuitableForReview(): bool
    {
        $pluginFirstUseTime = $this->pluginFirstUseTimeService->getPluginFirstUseTime();
        if (empty($pluginFirstUseTime)) {
            return false;
        }

        return $this->timestampIsThirtyDaysAgo($pluginFirstUseTime);
    }

    /**
     * Check if the review notice dismissed time is more than 30 days ago.
     */
    private function reviewNoticeDismissedTimeHasPassed(): bool
    {
        $reviewNoticeDismissedTime = get_option('simplybook_review_notice_dismissed_time');
        if (empty($reviewNoticeDismissedTime)) {
            return true; // default true to show the notice
        }

        return $this->timestampIsThirtyDaysAgo($reviewNoticeDismissedTime);
    }

    /**
     * Check if the timestamp is more than 30 days ago.
     * @param float|int|string $timestamp
     */
    private function timestampIsThirtyDaysAgo($timestamp): bool
    {
        $timestamp = Carbon::createFromTimestamp($timestamp);
        $thirtyDaysAgo = Carbon::now()->subDays(30);

        return $timestamp->isBefore($thirtyDaysAgo);
    }

    /**
     * Enqueue scripts for notice dismiss functionality
     */
    public function enqueueScripts(): void
    {
        // Only enqueue if the notice will be shown
        if ($this->canRenderReviewNotice() === false) {
            return;
        }

        $this->noticeDismissalService->enqueue();
    }

    /**
     * Get the amount of bookings from the SimplyBook API. This is cached in
     * the object for performance reasons. In the response from the API the
     * 'bookings' key value is the amount of bookings for the last 30 days.
     */
    private function getAmountOfBookings(): int
    {
        if (isset($this->bookingsAmount)) {
            return $this->bookingsAmount; // Object cache
        }

        $statistics = $this->client->get_statistics();
        if (empty($statistics)) {
            $this->bookingsAmount = 0;
            return $this->bookingsAmount;
        }

        $this->bookingsAmount = ($statistics['bookings'] ?? 0);
        return $this->bookingsAmount;
    }
}
