<?php
/**
 * Freemius Webhook Forwarder (CF17.1 Option B - client side).
 *
 * Listens to Freemius SDK action events for license lifecycle changes
 * (cancellation, refund, expiration, etc.) and forwards them to the
 * server's /v1/webhooks/status endpoint.
 *
 * This is the FAST path for license status sync:
 *   - Server cron (every 48h) = ground truth, catches silent changes
 *   - Client webhook (real-time) = fast path, instant on cancellation
 *
 * Both write to the same licenses.status column; whichever runs last wins.
 *
 * Security:
 *   - Webhook calls authenticated with X-SwapAds-Webhook-Secret header
 *   - Same secret the server uses; rotated via swapads_client_webhook_secret option
 *
 * @since 1.3.0
 */

declare(strict_types=1);

namespace SwapAds\Client\License;

use SwapAds\Client\Api\RestClient;

final class FreemiusWebhookForwarder
{
    public const OPTION_WEBHOOK_SECRET = 'swapads_client_webhook_secret';
    public const OPTION_LAST_FORWARD_AT = 'swapads_client_last_webhook_forward_at';
    public const OPTION_LAST_FORWARD_RESULT = 'swapads_client_last_webhook_forward_result';

    /**
     * Register Freemius action listeners.
     *
     * @return void
     *
     * @since 1.3.0
     */
    public static function register(): void
    {
        // Map Freemius SDK events to our server webhook event types.
        // SDK fires these actions when license state changes upstream.
        add_action('swa_fs_license_loaded', [self::class, 'onLicenseLoaded']);
        add_action('swa_fs_license_change', [self::class, 'onLicenseChange']);
        // Plugin-level events
        add_action('swa_fs_after_uninstall', [self::class, 'onAfterUninstall']);
        // Catch-all for any state change
        add_action('swa_fs_account_change', [self::class, 'onAccountChange']);
    }

    /**
     * Handle license_loaded event (SDK fires this once on first load after opt-in).
     *
     * @param object $license SDK license object.
     * @return void
     */
    public static function onLicenseLoaded($license): void
    {
        if (!is_object($license)) {
            return;
        }
        self::forward('license.loaded', [
            'license_id' => (string) ($license->id ?? ''),
            'plan_id'    => (string) ($license->plan_id ?? ''),
            'secret_key' => (string) ($license->secret_key ?? ''),
        ]);
    }

    /**
     * Handle license_change event (fires when license object updates).
     *
     * @param object $license SDK license object.
     * @return void
     */
    public static function onLicenseChange($license): void
    {
        if (!is_object($license)) {
            return;
        }
        self::forward('license.updated', [
            'license_id' => (string) ($license->id ?? ''),
            'plan_id'    => (string) ($license->plan_id ?? ''),
            'secret_key' => (string) ($license->secret_key ?? ''),
        ]);
    }

    /**
     * Handle account_change event (fires when FS account details change).
     *
     * @param object $account SDK account object.
     * @return void
     */
    public static function onAccountChange($account): void
    {
        if (!is_object($account)) {
            return;
        }
        // Map account changes to license status updates.
        $event = 'license.updated';
        if (isset($account->is_active) && !$account->is_active) {
            $event = 'license.cancelled';
        }
        self::forward($event, [
            'account_id' => (string) ($account->id ?? ''),
            'is_active'  => (bool) ($account->is_active ?? false),
        ]);
    }

    /**
     * Handle after_uninstall event (fires when user uninstalls plugin).
     *
     * @return void
     */
    public static function onAfterUninstall(): void
    {
        self::forward('license.cancelled', [
            'reason' => 'plugin_uninstalled',
        ]);
    }

    /**
     * Forward a Freemius event to the server's /v1/webhooks/status endpoint.
     *
     * @param string $eventType Event type (license.loaded, license.cancelled, etc.)
     * @param array<string, mixed> $payload Event payload.
     * @return bool True if forward succeeded.
     *
     * @since 1.3.0
     */
    public static function forward(string $eventType, array $payload): bool
    {
        if (!LicenseManager::isLicensed()) {
            return false;
        }

        $webhookSecret = (string) get_option(self::OPTION_WEBHOOK_SECRET, '');
        if ($webhookSecret === '') {
            // Operator hasn't configured webhook secret yet - silently skip.
            // The 48h cron will still catch license changes.
            return false;
        }

        $serverUrl = (string) get_option('swapads_client_server_url', '');
        if ($serverUrl === '') {
            return false;
        }

        // Build request with custom X-SwapAds-Webhook-Secret header.
        $url = rtrim($serverUrl, '/') . '/wp-json/swapads-server/v1/webhooks/status';
        $body = [
            'type' => $eventType,
        ] + $payload;

        $response = wp_remote_post($url, [
            'headers' => [
                'Content-Type' => 'application/json',
                'X-SwapAds-Webhook-Secret' => $webhookSecret,
            ],
            'body' => (string) json_encode($body),
            'timeout' => 10,
        ]);

        update_option(self::OPTION_LAST_FORWARD_AT, time());

        if (!is_array($response)) {
            update_option(self::OPTION_LAST_FORWARD_RESULT, [
                'success' => false,
                'error' => 'wp_remote_post returned non-array response',
                'event' => $eventType,
            ]);
            return false;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $raw = (string) wp_remote_retrieve_body($response);
        $data = json_decode($raw, true);
        $success = $code >= 200 && $code < 300;

        update_option(self::OPTION_LAST_FORWARD_RESULT, [
            'success' => $success,
            'http_code' => $code,
            'event' => $eventType,
            'response' => is_array($data) ? $data : [],
        ]);

        return $success;
    }

    /**
     * Get the last forward result (for diagnostics in admin UI).
     *
     * @return array<string, mixed>
     */
    public static function getLastResult(): array
    {
        $result = get_option(self::OPTION_LAST_FORWARD_RESULT, []);
        return is_array($result) ? $result : [];
    }
}