<?php

namespace ArraySubs\Features\MainAdmin\REST;

use ArraySubs\Supports\BaseRestController;
use WP_REST_Request;
use WP_REST_Response;

defined('ABSPATH') || exit;

/**
 * License REST controller.
 */
class LicenseController extends BaseRestController
{
    private const OPTION_NAME = 'arraysubs_license';
    private const DEFAULT_LICENSE_API_URL = 'https://user-portal.arrayhash.com/api/license/activate';
    private const PLUGIN_SLUG = 'arraysubspro';

    /**
     * Register REST API routes.
     *
     * @return void
     */
    public function registerRoutes(): void
    {
        \register_rest_route(
            $this->namespace,
            '/license',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'getStatus'],
                'permission_callback' => [$this, 'checkAdminPermission'],
            ]
        );

        \register_rest_route(
            $this->namespace,
            '/license',
            [
                'methods'             => 'DELETE',
                'callback'            => [$this, 'remove'],
                'permission_callback' => [$this, 'checkAdminPermission'],
            ]
        );

        \register_rest_route(
            $this->namespace,
            '/license/activate',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'activate'],
                'permission_callback' => [$this, 'checkAdminPermission'],
                'args'                => [
                    'license_key' => [
                        'required'          => true,
                        'type'              => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ]
        );
    }

    /**
     * Remove the locally stored license from WordPress only.
     *
     * Intentionally local: no portal or license-server call. The same domain
     * (or another subdirectory install on it) can re-activate any time and
     * receives the same activation, so removing here never disturbs sibling
     * installs sharing the domain's seat.
     *
     * @return WP_REST_Response
     */
    public function remove(): WP_REST_Response
    {
        \delete_option(self::OPTION_NAME);

        return \arraysubs_rest_response(
            ['active' => false],
            200,
            \esc_html__('License removed from this WordPress site.', 'arraysubs')
        );
    }

    /**
     * Check admin permission for license endpoints.
     *
     * @return bool
     */
    public function checkAdminPermission(): bool
    {
        return \current_user_can('manage_woocommerce') || \current_user_can('manage_options');
    }

    /**
     * Check whether the locally stored ArraySubs Pro license is usable now.
     *
     * This is intentionally a local status check. It does not call the remote
     * license server; it verifies the activation metadata already stored in WP.
     *
     * @return bool
     */
    public static function status(): bool
    {
        if (!self::isProActive()) {
            return false;
        }

        $license = self::getStoredLicense();

        if (!$license) {
            return false;
        }

        return self::isStoredLicenseValid($license, self::getActivationDomain(), \gmdate('c'));
    }

    /**
     * Get stored license status.
     *
     * @return WP_REST_Response
     */
    public function getStatus(): WP_REST_Response
    {
        if (!self::isProActive()) {
            return \arraysubs_rest_response(
                ['active' => false],
                403,
                \esc_html__('Activate ArraySubs Pro first, then return here to manage the license.', 'arraysubs')
            );
        }

        $license = self::getStoredLicense();

        if (!$license) {
            return \arraysubs_rest_response(
                ['active' => false],
                200,
                \esc_html__('No license is activated.', 'arraysubs')
            );
        }

        if (!self::isStoredLicenseValid($license, self::getActivationDomain(), \gmdate('c'))) {
            return \arraysubs_rest_response(
                [
                    'active'  => false,
                    'license' => $license,
                ],
                200,
                \esc_html__('The stored license is expired or does not match this site domain.', 'arraysubs')
            );
        }

        return \arraysubs_rest_response(
            [
                'active'  => true,
                'license' => $license,
            ],
            200,
            \esc_html__('License is activated.', 'arraysubs')
        );
    }

    /**
     * Activate and store a license.
     *
     * @param WP_REST_Request $request Request object.
     * @return WP_REST_Response
     */
    public function activate(WP_REST_Request $request): WP_REST_Response
    {
        if (!self::isProActive()) {
            return \arraysubs_rest_response(
                [],
                403,
                \esc_html__('Activate ArraySubs Pro first, then return here to activate the license.', 'arraysubs')
            );
        }

        $license_key = $this->normalizeLicenseKey((string) $request->get_param('license_key'));
        $activated_domain = self::getActivationDomain();

        if (!$this->isValidLicenseKey($license_key)) {
            return \arraysubs_rest_response(
                [],
                400,
                \esc_html__('Enter the license key from the license page in your ArrayHash portal.', 'arraysubs')
            );
        }

        if ('' === $activated_domain) {
            return \arraysubs_rest_response(
                [],
                400,
                \esc_html__('Could not determine this site domain for activation. Check the WordPress Site Address setting and try again.', 'arraysubs')
            );
        }

        $activation_date_utc = \gmdate('c');
        $remote_license      = $this->fetchRemoteLicense($license_key, $activation_date_utc, $activated_domain);

        if (isset($remote_license['error'])) {
            return \arraysubs_rest_response(
                [],
                (int) ($remote_license['status'] ?? 400),
                (string) $remote_license['error']
            );
        }

        $license = $remote_license['license'] ?? null;

        if (!self::isLicensePayloadValid($license, $license_key, $activation_date_utc, $activated_domain)) {
            return \arraysubs_rest_response(
                [],
                400,
                \esc_html__('The license server response was incomplete or did not match this site. Please try again.', 'arraysubs')
            );
        }

        $activation = $license['activation'];
        $stored_license = [
            'licenseKey'         => sanitize_text_field((string) $license_key),
            'activationKey'      => sanitize_text_field((string) $activation['activationKey']),
            'activatedDomain'    => sanitize_text_field((string) $activation['activatedDomain']),
            'pluginSlug'         => self::PLUGIN_SLUG,
            'status'             => sanitize_text_field((string) ($license['status'] ?? 'active')),
            'product'            => sanitize_text_field((string) ($license['product'] ?? '')),
            'plan'               => sanitize_text_field((string) ($license['plan'] ?? '')),
            'createdDateUtc'     => sanitize_text_field((string) $license['createdDateUtc']),
            'expireDateUtc'      => sanitize_text_field((string) ($license['expireDateUtc'] ?? '')),
            'activationDateUtc'  => $activation_date_utc,
            'lastStatusCheckUtc' => $activation_date_utc,
        ];

        if (!self::isStoredLicenseValid($stored_license, $activated_domain, $activation_date_utc)) {
            return \arraysubs_rest_response(
                [],
                400,
                \esc_html__('The activated license is expired or does not match this site domain.', 'arraysubs')
            );
        }

        \update_option(self::OPTION_NAME, \wp_json_encode($stored_license), false);

        return \arraysubs_rest_response(
            [
                'active'  => true,
                'license' => $stored_license,
            ],
            200,
            \esc_html__('License activated successfully.', 'arraysubs')
        );
    }

    /**
     * Check whether ArraySubs Pro is active.
     *
     * @return bool
     */
    private static function isProActive(): bool
    {
        return \defined('ARRAYSUBSPRO_VERSION');
    }

    /**
     * Normalize a submitted license key.
     *
     * Freemius license keys are case-sensitive and may contain symbols, so
     * only surrounding whitespace is stripped.
     *
     * @param string $license_key Raw license key.
     * @return string
     */
    private function normalizeLicenseKey(string $license_key): string
    {
        return trim($license_key);
    }

    /**
     * Validate license key shape (a Freemius license secret key).
     *
     * @param string $license_key License key.
     * @return bool
     */
    private function isValidLicenseKey(string $license_key): bool
    {
        return 1 === preg_match('/^\S{10,128}$/', $license_key);
    }

    /**
     * Activate the license through the ArrayHash user-portal license API.
     *
     * The portal validates the license on Freemius and only activates when
     * this site's domain was pre-registered on the license page.
     *
     * @param string $license_key License key.
     * @param string $activation_date_utc Activation date in UTC.
     * @param string $activated_domain Activation domain.
     * @return array<string, mixed>
     */
    private function fetchRemoteLicense(string $license_key, string $activation_date_utc, string $activated_domain): array
    {
        unset($activation_date_utc);

        $api_url = $this->getLicenseApiUrl();
        $response = \wp_remote_post(
            $api_url,
            [
                'timeout' => 20,
                'headers' => [
                    'Content-Type' => 'application/json',
                    'Accept'       => 'application/json',
                ],
                'body' => \wp_json_encode(
                    [
                        'licenseKey'    => $license_key,
                        'pluginSlug'    => self::PLUGIN_SLUG,
                        'domain'        => $activated_domain,
                        'siteTitle'     => \get_bloginfo('name'),
                        'pluginVersion' => \defined('ARRAYSUBSPRO_VERSION') ? (string) ARRAYSUBSPRO_VERSION : '',
                    ]
                ),
            ]
        );

        if (\is_wp_error($response)) {
            return [
                'status' => 502,
                'error'  => $response->get_error_message(),
            ];
        }

        $status = (int) \wp_remote_retrieve_response_code($response);
        $body   = \json_decode((string) \wp_remote_retrieve_body($response), true);

        if (!is_array($body)) {
            return [
                'status' => 502,
                'error'  => \esc_html__('The license server returned an invalid response. Please try again later.', 'arraysubs'),
            ];
        }

        if ($status < 200 || $status >= 300 || empty($body['ok'])) {
            return [
                'status' => $status >= 400 ? $status : 400,
                'error'  => sanitize_text_field((string) ($body['error'] ?? __('The license key could not be validated. Check the key and try again.', 'arraysubs'))),
            ];
        }

        return $body;
    }

    /**
     * Get the remote license API URL.
     *
     * @return string
     */
    private function getLicenseApiUrl(): string
    {
        $default_url = \defined('ARRAYSUBS_LICENSE_API_URL')
            ? (string) ARRAYSUBS_LICENSE_API_URL
            : self::DEFAULT_LICENSE_API_URL;

        /**
         * Filter the remote license API URL.
         *
         * @param string $default_url Default URL.
         */
        return \esc_url_raw((string) \apply_filters('arraysubs_license_api_url', $default_url));
    }

    /**
     * Validate the returned license payload and activation window.
     *
     * @param mixed  $license License payload.
     * @param string $license_key Expected license key.
     * @param string $activation_date_utc Activation date.
     * @param string $activated_domain Expected activated domain.
     * @return bool
     */
    private static function isLicensePayloadValid($license, string $license_key, string $activation_date_utc, string $activated_domain): bool
    {
        if (!is_array($license)) {
            return false;
        }

        if (($license['licenseKey'] ?? '') !== $license_key) {
            return false;
        }

        if (($license['pluginSlug'] ?? '') !== self::PLUGIN_SLUG) {
            return false;
        }

        $activation = $license['activation'] ?? null;

        if (!is_array($activation)) {
            return false;
        }

        if (!isset($activation['activationKey']) || 1 !== preg_match('/^[a-f0-9]{16}$/', (string) $activation['activationKey'])) {
            return false;
        }

        if (($activation['licenseKey'] ?? '') !== $license_key) {
            return false;
        }

        if (($activation['activatedDomain'] ?? '') !== $activated_domain) {
            return false;
        }

        return self::isDateWithinLicenseWindow($license, $activation_date_utc);
    }

    /**
     * Validate a stored license against the current site and date.
     *
     * @param array<string, mixed> $license Stored license metadata.
     * @param string               $current_domain Current site activation domain.
     * @param string               $validation_date_utc Validation date in UTC.
     * @return bool
     */
    private static function isStoredLicenseValid(array $license, string $current_domain, string $validation_date_utc): bool
    {
        if (($license['pluginSlug'] ?? '') !== self::PLUGIN_SLUG) {
            return false;
        }

        // Periodic status checks flip this away from "active" when the
        // license is cancelled or expired on the licensing server.
        if ('active' !== (string) ($license['status'] ?? 'active')) {
            return false;
        }

        if (1 !== preg_match('/^[a-f0-9]{16}$/', (string) ($license['activationKey'] ?? ''))) {
            return false;
        }

        if ('' === $current_domain || (($license['activatedDomain'] ?? '') !== $current_domain)) {
            return false;
        }

        if (!self::isDateWithinLicenseWindow($license, $validation_date_utc)) {
            return false;
        }

        $activation_date_utc = (string) ($license['activationDateUtc'] ?? '');

        if ('' === $activation_date_utc) {
            return false;
        }

        return self::isDateWithinLicenseWindow($license, $activation_date_utc);
    }

    /**
     * Check whether a UTC date is inside a license's created/expires window.
     *
     * @param array<string, mixed> $license License payload.
     * @param string               $date_utc Date to validate.
     * @return bool
     */
    private static function isDateWithinLicenseWindow(array $license, string $date_utc): bool
    {
        $created = self::parseUtcTimestamp((string) ($license['createdDateUtc'] ?? ''));
        $checked = self::parseUtcTimestamp($date_utc);

        if (null === $created || null === $checked) {
            return false;
        }

        $expire_raw = trim((string) ($license['expireDateUtc'] ?? ''));

        // Lifetime licenses have no expiration date.
        if ('' === $expire_raw) {
            return $checked >= $created;
        }

        $expires = self::parseUtcTimestamp($expire_raw);

        if (null === $expires || $expires < $created) {
            return false;
        }

        return $checked >= $created && $checked <= $expires;
    }

    /**
     * Parse a UTC date string into a Unix timestamp.
     *
     * @param string $date Date string.
     * @return int|null
     */
    private static function parseUtcTimestamp(string $date): ?int
    {
        $date = trim($date);

        if ('' === $date) {
            return null;
        }

        try {
            return (new \DateTimeImmutable($date))->getTimestamp();
        } catch (\Exception $exception) {
            return null;
        }
    }

    /**
     * Get the current site activation domain without any subdirectory path.
     *
     * @return string
     */
    private static function getActivationDomain(): string
    {
        $url = \home_url('/');
        $host = \wp_parse_url($url, PHP_URL_HOST);

        if (!is_string($host) || '' === $host) {
            return '';
        }

        $domain = strtolower(rtrim($host, '.'));
        $port = \wp_parse_url($url, PHP_URL_PORT);

        if (is_int($port)) {
            $domain .= ':' . $port;
        }

        return $domain;
    }

    /**
     * Get stored license metadata.
     *
     * @return array<string, mixed>|null
     */
    private static function getStoredLicense(): ?array
    {
        $raw = \get_option(self::OPTION_NAME, '');

        if (!is_string($raw) || '' === $raw) {
            return null;
        }

        $license = \json_decode($raw, true);

        return is_array($license) ? self::sanitizeLicenseArray($license) : null;
    }

    /**
     * Sanitize a license array for storage/response.
     *
     * @param array<string, mixed> $value License data.
     * @return array<string, mixed>
     */
    private static function sanitizeLicenseArray(array $value): array
    {
        $sanitized = [];

        foreach ($value as $key => $item) {
            $sanitized_key = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $key);

            if ('' === $sanitized_key) {
                continue;
            }

            if (is_array($item)) {
                $sanitized[$sanitized_key] = self::sanitizeLicenseArray($item);
                continue;
            }

            if (is_bool($item) || is_int($item) || is_float($item) || null === $item) {
                $sanitized[$sanitized_key] = $item;
                continue;
            }

            $sanitized[$sanitized_key] = sanitize_text_field((string) $item);
        }

        return $sanitized;
    }
}
