<?php
/**
 * Update Controller
 *
 * REST API endpoint for plugin update checks
 *
 * @package Jeremias\WcLicensedProduct\Api
 */

declare(strict_types=1);

namespace Jeremias\WcLicensedProduct\Api;

use Jeremias\WcLicensedProduct\License\LicenseManager;
use Jeremias\WcLicensedProduct\Product\VersionManager;
use Jeremias\WcLicensedProduct\Product\ProductVersion;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Handles REST API endpoint for plugin update checks
 *
 * This endpoint allows licensed plugins to check for updates from this WooCommerce store.
 * It validates the license and returns WordPress-compatible update information.
 */
final class UpdateController
{
    use IpDetectionTrait;
    private const NAMESPACE = 'wc-licensed-product/v1';

    /**
     * Default rate limit: requests per window per IP
     */
    private const DEFAULT_RATE_LIMIT = 30;

    /**
     * Default rate limit window in seconds
     */
    private const DEFAULT_RATE_WINDOW = 60;

    private LicenseManager $licenseManager;
    private VersionManager $versionManager;

    public function __construct(LicenseManager $licenseManager, VersionManager $versionManager)
    {
        $this->licenseManager = $licenseManager;
        $this->versionManager = $versionManager;
        $this->registerHooks();
    }

    /**
     * Register WordPress hooks
     */
    private function registerHooks(): void
    {
        add_action('rest_api_init', [$this, 'registerRoutes']);
    }

    /**
     * Get the configured rate limit (requests per window)
     */
    private function getRateLimit(): int
    {
        return defined('WC_LICENSE_RATE_LIMIT')
            ? (int) WC_LICENSE_RATE_LIMIT
            : self::DEFAULT_RATE_LIMIT;
    }

    /**
     * Get the configured rate limit window in seconds
     */
    private function getRateWindow(): int
    {
        return defined('WC_LICENSE_RATE_WINDOW')
            ? (int) WC_LICENSE_RATE_WINDOW
            : self::DEFAULT_RATE_WINDOW;
    }

    /**
     * Check rate limit for current IP
     *
     * @return WP_REST_Response|null Returns error response if rate limited, null if OK
     */
    private function checkRateLimit(): ?WP_REST_Response
    {
        $ip = $this->getClientIp();
        $transientKey = 'wclp_update_rate_' . md5($ip);
        $rateLimit = $this->getRateLimit();
        $rateWindow = $this->getRateWindow();

        $data = get_transient($transientKey);

        if ($data === false) {
            set_transient($transientKey, ['count' => 1, 'start' => time()], $rateWindow);
            return null;
        }

        $count = (int) ($data['count'] ?? 0);
        $start = (int) ($data['start'] ?? time());

        if (time() - $start >= $rateWindow) {
            set_transient($transientKey, ['count' => 1, 'start' => time()], $rateWindow);
            return null;
        }

        if ($count >= $rateLimit) {
            $retryAfter = $rateWindow - (time() - $start);
            $response = new WP_REST_Response([
                'success' => false,
                'error' => 'rate_limit_exceeded',
                'message' => __('Too many requests. Please try again later.', 'wc-licensed-product'),
                'retry_after' => $retryAfter,
            ], 429);
            $response->header('Retry-After', (string) $retryAfter);
            return $response;
        }

        set_transient($transientKey, ['count' => $count + 1, 'start' => $start], $rateWindow);
        return null;
    }

    /**
     * Register REST API routes
     */
    public function registerRoutes(): void
    {
        register_rest_route(self::NAMESPACE, '/update-check', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'handleUpdateCheck'],
            'permission_callback' => '__return_true',
            'args' => [
                'license_key' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ($value): bool {
                        $len = strlen($value);
                        return !empty($value) && $len >= 8 && $len <= 64;
                    },
                ],
                'domain' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                    'validate_callback' => function ($value): bool {
                        return !empty($value) && strlen($value) <= 255;
                    },
                ],
                'plugin_slug' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
                'current_version' => [
                    'required' => false,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field',
                ],
            ],
        ]);
    }

    /**
     * Handle update check request
     */
    public function handleUpdateCheck(WP_REST_Request $request): WP_REST_Response
    {
        $rateLimitResponse = $this->checkRateLimit();
        if ($rateLimitResponse !== null) {
            return $rateLimitResponse;
        }

        $licenseKey = $request->get_param('license_key');
        $domain = $request->get_param('domain');
        $currentVersion = $request->get_param('current_version');

        // Validate license
        $validationResult = $this->licenseManager->validateLicense($licenseKey, $domain);

        if (!$validationResult['valid']) {
            return new WP_REST_Response([
                'success' => false,
                'update_available' => false,
                'error' => $validationResult['error'] ?? 'license_invalid',
                'message' => $validationResult['message'] ?? __('License validation failed.', 'wc-licensed-product'),
            ], $validationResult['error'] === 'license_not_found' ? 404 : 403);
        }

        // Get license to access product ID
        $license = $this->licenseManager->getLicenseByKey($licenseKey);
        if (!$license) {
            return new WP_REST_Response([
                'success' => false,
                'update_available' => false,
                'error' => 'license_not_found',
                'message' => __('License not found.', 'wc-licensed-product'),
            ], 404);
        }

        $productId = $license->getProductId();
        $product = wc_get_product($productId);

        if (!$product) {
            return new WP_REST_Response([
                'success' => false,
                'update_available' => false,
                'error' => 'product_not_found',
                'message' => __('Licensed product not found.', 'wc-licensed-product'),
            ], 404);
        }

        // Get latest version based on major version binding
        $latestVersion = $this->getLatestVersionForLicense($license);

        if (!$latestVersion) {
            return new WP_REST_Response([
                'success' => true,
                'update_available' => false,
                'version' => $currentVersion ?? '0.0.0',
                'message' => __('No versions available for this product.', 'wc-licensed-product'),
            ]);
        }

        // Check if update is available
        $updateAvailable = $currentVersion
            ? version_compare($latestVersion->getVersion(), $currentVersion, '>')
            : true;

        // Build response
        $response = $this->buildUpdateResponse($product, $latestVersion, $license, $updateAvailable);

        return new WP_REST_Response($response);
    }

    /**
     * Get latest version for a license, respecting major version binding
     */
    private function getLatestVersionForLicense($license): ?ProductVersion
    {
        $productId = $license->getProductId();

        // Check if license is bound to a specific version
        $versionId = $license->getVersionId();
        if ($versionId) {
            $boundVersion = $this->versionManager->getVersionById($versionId);
            if ($boundVersion) {
                // Get latest version for this major version
                return $this->versionManager->getLatestVersionForMajor(
                    $productId,
                    $boundVersion->getMajorVersion()
                );
            }
        }

        // No version binding, return latest overall
        return $this->versionManager->getLatestVersion($productId);
    }

    /**
     * Build WordPress-compatible update response
     */
    private function buildUpdateResponse($product, ProductVersion $version, $license, bool $updateAvailable): array
    {
        $productSlug = $product->get_slug();

        // Generate secure download URL
        $downloadUrl = $this->generateUpdateDownloadUrl($license->getId(), $version->getId());

        $response = [
            'success' => true,
            'update_available' => $updateAvailable,
            'version' => $version->getVersion(),
            'slug' => $productSlug,
            'plugin' => $productSlug . '/' . $productSlug . '.php',
            'download_url' => $downloadUrl,
            'package' => $downloadUrl,
            'last_updated' => $version->getReleasedAt()->format('Y-m-d'),
            'tested' => $this->getTestedWpVersion(),
            'requires' => $this->getRequiredWpVersion(),
            'requires_php' => $this->getRequiredPhpVersion(),
        ];

        // Add changelog if available
        if ($version->getReleaseNotes()) {
            $response['changelog'] = $version->getReleaseNotes();
            $response['sections'] = [
                'description' => $product->get_short_description() ?: $product->get_description(),
                'changelog' => $version->getReleaseNotes(),
            ];
        }

        // Add package hash for integrity verification
        if ($version->getFileHash()) {
            $response['package_hash'] = 'sha256:' . $version->getFileHash();
        }

        // Add product name and homepage
        $response['name'] = $product->get_name();
        $response['homepage'] = get_permalink($product->get_id());

        // Add icons if product has featured image
        $imageId = $product->get_image_id();
        if ($imageId) {
            $iconUrl = wp_get_attachment_image_url($imageId, 'thumbnail');
            $iconUrl2x = wp_get_attachment_image_url($imageId, 'medium');
            if ($iconUrl) {
                $response['icons'] = [
                    '1x' => $iconUrl,
                    '2x' => $iconUrl2x ?: $iconUrl,
                ];
            }
        }

        return $response;
    }

    /**
     * Generate secure download URL for updates
     */
    private function generateUpdateDownloadUrl(int $licenseId, int $versionId): string
    {
        $data = $licenseId . '-' . $versionId . '-' . wp_salt('auth');
        $hash = substr(hash('sha256', $data), 0, 16);
        $downloadKey = $licenseId . '-' . $versionId . '-' . $hash;

        return home_url('license-download/' . $downloadKey);
    }

    /**
     * Get tested WordPress version from plugin headers
     */
    private function getTestedWpVersion(): string
    {
        return get_option('wc_licensed_product_tested_wp', '6.7');
    }

    /**
     * Get required WordPress version from plugin headers
     */
    private function getRequiredWpVersion(): string
    {
        return get_option('wc_licensed_product_requires_wp', '6.0');
    }

    /**
     * Get required PHP version
     */
    private function getRequiredPhpVersion(): string
    {
        return get_option('wc_licensed_product_requires_php', '8.3');
    }
}
