<?php
/**
 * Software Licensing Service
 * 
 * Handles license key generation, validation, and management for Instinctor add-ons.
 * Used on instinctor.com to sell licenses, and in Instinctor plugin to verify them.
 * 
 * @package Instinctor
 * @since 1.4.1
 */

declare(strict_types=1);

namespace Instinctor\Services;

use Instinctor\Core\Config;
use Instinctor\Core\DB;
use Instinctor\Core\Router;

if ( ! defined( 'ABSPATH' ) ) exit;

class SoftwareLicensingService
{
    private static ?self $instance = null;
    
    private string $table_licenses;
    private string $table_activations;
    private string $api_endpoint;

    public static function instance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct()
    {
        $this->table_licenses = DB::prefix() . 'instinctor_licenses';
        $this->table_activations = DB::prefix() . 'instinctor_license_activations';
        $this->api_endpoint = 'https://instinctor.com/wp-json/instinctor/v1/license';
        
        // REST API endpoints for license server
        add_action('rest_api_init', [$this, 'registerRestRoutes']);
        
        // Hook into order completion to generate licenses
        add_action('instinctor_order_completed', [$this, 'generateLicenseForOrder']);
        
        // Send license delivery email when a license is created
        add_action('instinctor_license_created', [$this, 'sendLicenseDeliveryEmail'], 10, 2);
        
        // AJAX handlers
        $this->registerAjax('instinctor_verify_license', [$this, 'ajaxVerifyLicense']);
        add_action('wp_ajax_nopriv_instinctor_verify_license', [$this, 'ajaxVerifyLicense']);
    }

    /**
     * Register REST API routes for license validation
     */
    public function registerRestRoutes(): void
    {
        Router::restRaw('/license/verify', [
            'methods' => 'POST',
            'callback' => [$this, 'restVerifyLicense'],
            'permission_callback' => '__return_true',
            'args' => [
                'license_key' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field'
                ],
                'addon' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'sanitize_text_field'
                ],
                'site_url' => [
                    'required' => true,
                    'type' => 'string',
                    'sanitize_callback' => 'esc_url_raw'
                ]
            ]
        ]);

        Router::restRaw('/license/activate', [
            'methods' => 'POST',
            'callback' => [$this, 'restActivateLicense'],
            'permission_callback' => '__return_true',
            'args' => [
                'license_key' => [
                    'required' => true,
                    'type' => 'string'
                ],
                'addon' => [
                    'required' => true,
                    'type' => 'string'
                ],
                'site_url' => [
                    'required' => true,
                    'type' => 'string'
                ]
            ]
        ]);

        Router::restRaw('/license/deactivate', [
            'methods' => 'POST',
            'callback' => [$this, 'restDeactivateLicense'],
            'permission_callback' => '__return_true',
            'args' => [
                'license_key' => [
                    'required' => true,
                    'type' => 'string'
                ],
                'site_url' => [
                    'required' => true,
                    'type' => 'string'
                ]
            ]
        ]);

        Router::restRaw('/license/check', [
            'methods' => 'GET',
            'callback' => [$this, 'restCheckLicense'],
            'permission_callback' => '__return_true',
            'args' => [
                'license_key' => [
                    'required' => true,
                    'type' => 'string'
                ]
            ]
        ]);
    }

    /**
     * Generate a unique license key
     */
    public function generateLicenseKey(): string
    {
        $segments = [];
        for ($i = 0; $i < 4; $i++) {
            $segments[] = strtoupper(substr(md5(uniqid((string)wp_rand(), true)), 0, 4));
        }
        return implode('-', $segments);
    }

    /**
     * Create a new license
     */
    public function createLicense(array $data): ?int
    {
        
        $license_key = $this->generateLicenseKey();
        
        // Ensure unique
        while ($this->getLicenseByKey($license_key)) {
            $license_key = $this->generateLicenseKey();
        }
        
        $defaults = [
            'license_key' => $license_key,
            'addon' => '',
            'customer_id' => 0,
            'customer_email' => '',
            'order_id' => 0,
            'activation_limit' => 1,
            'status' => 'active',
            // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
            'expires_at' => date('Y-m-d H:i:s', strtotime('+1 year')),
            // phpcs:enable WordPress.DateTime.RestrictedFunctions
        ];
        
        $data = array_merge($defaults, $data);
        $data['license_key'] = $license_key;
        $data['created_at'] = current_time('mysql');
        
        $result = DB::insert($this->table_licenses, $data);
        
        if ($result) {
            do_action('instinctor_license_created', DB::insert_id(), $data);
            return DB::insert_id();
        }
        
        return null;
    }

    /**
     * Get license by key
     */
    public function getLicenseByKey(string $key): ?object
    {
        
        return DB::get_row(DB::prepare(
            "SELECT * FROM {$this->table_licenses} WHERE license_key = %s",
            $key
        ));
    }

    /**
     * Get licenses by customer
     */
    public function getLicensesByCustomer(int $customer_id): array
    {
        
        return DB::get_results(DB::prepare(
            "SELECT * FROM {$this->table_licenses} 
             WHERE customer_id = %d 
             ORDER BY created_at DESC",
            $customer_id
        ));
    }

    /**
     * Get licenses by order
     */
    public function getLicensesByOrder(int $order_id): array
    {
        
        return DB::get_results(DB::prepare(
            "SELECT * FROM {$this->table_licenses} WHERE order_id = %d",
            $order_id
        ));
    }

    /**
     * Verify a license key
     */
    public function verifyLicense(string $key, string $addon, string $site_url): array
    {
        $license = $this->getLicenseByKey($key);
        
        if (!$license) {
            return [
                'valid' => false,
                'error' => 'invalid_key',
                'message' => 'License key not found'
            ];
        }
        
        // Check addon match
        if ($license->addon !== $addon && $license->addon !== 'all_access') {
            return [
                'valid' => false,
                'error' => 'addon_mismatch',
                'message' => 'License is not valid for this add-on'
            ];
        }
        
        // Check status
        if ($license->status !== 'active') {
            return [
                'valid' => false,
                'error' => 'license_' . $license->status,
                'message' => 'License is ' . $license->status
            ];
        }
        
        // Check expiration
        if ($license->expires_at && strtotime($license->expires_at) < time()) {
            return [
                'valid' => false,
                'error' => 'license_expired',
                'message' => 'License has expired',
                'expires_at' => $license->expires_at
            ];
        }
        
        // Check if site is already activated
        $activation = $this->getActivation($license->id, $site_url);
        
        if ($activation) {
            return [
                'valid' => true,
                'license_id' => $license->id,
                'addon' => $license->addon,
                'expires_at' => $license->expires_at,
                'activated' => true
            ];
        }
        
        // Check activation limit
        $activation_count = $this->getActivationCount($license->id);
        
        if ($activation_count >= $license->activation_limit) {
            return [
                'valid' => false,
                'error' => 'activation_limit',
                'message' => 'Activation limit reached (' . $license->activation_limit . ' sites)',
                'activations' => $activation_count,
                'limit' => $license->activation_limit
            ];
        }
        
        return [
            'valid' => true,
            'license_id' => $license->id,
            'addon' => $license->addon,
            'expires_at' => $license->expires_at,
            'activated' => false,
            'activations_remaining' => $license->activation_limit - $activation_count
        ];
    }

    /**
     * Activate a license on a site
     */
    public function activateLicense(string $key, string $addon, string $site_url): array
    {
        $verification = $this->verifyLicense($key, $addon, $site_url);
        
        if (!$verification['valid']) {
            return $verification;
        }
        
        if ($verification['activated'] ?? false) {
            return [
                'success' => true,
                'message' => 'License already activated on this site'
            ];
        }
        
        
        $license = $this->getLicenseByKey($key);
        
        $result = DB::insert($this->table_activations, [
            'license_id' => $license->id,
            'site_url' => $this->normalizeSiteUrl($site_url),
            'activated_at' => current_time('mysql'),
            'last_check' => current_time('mysql'),
            'status' => 'active'
        ]);
        
        if ($result) {
            do_action('instinctor_license_activated', $license->id, $site_url);
            
            return [
                'success' => true,
                'message' => 'License activated successfully',
                'license_id' => $license->id,
                'expires_at' => $license->expires_at
            ];
        }
        
        return [
            'success' => false,
            'error' => 'activation_failed',
            'message' => 'Failed to activate license'
        ];
    }

    /**
     * Deactivate a license from a site
     */
    public function deactivateLicense(string $key, string $site_url): array
    {
        
        $license = $this->getLicenseByKey($key);
        
        if (!$license) {
            return [
                'success' => false,
                'error' => 'invalid_key',
                'message' => 'License key not found'
            ];
        }
        
        $site_url = $this->normalizeSiteUrl($site_url);
        
        $result = DB::delete($this->table_activations, [
            'license_id' => $license->id,
            'site_url' => $site_url
        ]);
        
        if ($result) {
            do_action('instinctor_license_deactivated', $license->id, $site_url);
            
            return [
                'success' => true,
                'message' => 'License deactivated successfully'
            ];
        }
        
        return [
            'success' => false,
            'error' => 'not_activated',
            'message' => 'License was not activated on this site'
        ];
    }

    /**
     * Get activation record
     */
    public function getActivation(int $license_id, string $site_url): ?object
    {
        
        return DB::get_row(DB::prepare(
            "SELECT * FROM {$this->table_activations} 
             WHERE license_id = %d AND site_url = %s AND status = 'active'",
            $license_id,
            $this->normalizeSiteUrl($site_url)
        ));
    }

    /**
     * Get activation count for a license
     */
    public function getActivationCount(int $license_id): int
    {
        
        return (int) DB::get_var(DB::prepare(
            "SELECT COUNT(*) FROM {$this->table_activations} 
             WHERE license_id = %d AND status = 'active'",
            $license_id
        ));
    }

    /**
     * Get all activations for a license
     */
    public function getActivations(int $license_id): array
    {
        
        return DB::get_results(DB::prepare(
            "SELECT * FROM {$this->table_activations} 
             WHERE license_id = %d 
             ORDER BY activated_at DESC",
            $license_id
        ));
    }

    /**
     * Update license status
     */
    public function updateLicenseStatus(int $license_id, string $status): bool
    {
        
        $valid_statuses = ['active', 'expired', 'suspended', 'revoked'];
        
        if (!in_array($status, $valid_statuses)) {
            return false;
        }
        
        $result = DB::update(
            $this->table_licenses,
            ['status' => $status, 'updated_at' => current_time('mysql')],
            ['id' => $license_id]
        );
        
        if ($result !== false) {
            do_action('instinctor_license_status_changed', $license_id, $status);
            return true;
        }
        
        return false;
    }

    /**
     * Renew a license
     */
    public function renewLicense(int $license_id, int $days = 365): bool
    {
        
        $license = DB::get_row(DB::prepare(
            "SELECT * FROM {$this->table_licenses} WHERE id = %d",
            $license_id
        ));
        
        if (!$license) {
            return false;
        }
        
        // Calculate new expiration from current expiration or now
        $base_date = $license->expires_at && strtotime($license->expires_at) > time()
            ? $license->expires_at
            : current_time('mysql');
        
        // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
        $new_expiration = date('Y-m-d H:i:s', strtotime($base_date . " +{$days} days"));
        // phpcs:enable WordPress.DateTime.RestrictedFunctions
        
        $result = DB::update(
            $this->table_licenses,
            [
                'expires_at' => $new_expiration,
                'status' => 'active',
                'updated_at' => current_time('mysql')
            ],
            ['id' => $license_id]
        );
        
        if ($result !== false) {
            do_action('instinctor_license_renewed', $license_id, $new_expiration);
            return true;
        }
        
        return false;
    }

    /**
     * Generate license for completed order
     */
    /**
     * Send license delivery email when a license is created
     */
    public function sendLicenseDeliveryEmail(int $licenseId, array $data): void
    {
        $p = DB::prefix();
        $email = $data['customer_email'] ?? '';
        if (!$email || !is_email($email)) {
            return;
        }

        // Get customer name
        $customer = DB::get_row(DB::prepare(
            "SELECT first_name, last_name FROM {$p}instinctor_customers WHERE id = %d",
            intval($data['customer_id'] ?? 0)
        ));
        $customerName = $customer
            ? trim(($customer->first_name ?? '') . ' ' . ($customer->last_name ?? ''))
            : '';
        $customerName = $customerName ?: $email;

        $expiresAt = $data['expires_at'] ?? '';
        $expiresFormatted = $expiresAt
            ? date_i18n(Config::get('date_format', 'Y-m-d'), strtotime($expiresAt))
            : __('Never', 'instinctor');

        $pricingUrl = Config::get('instinctor_pricing_page_url', home_url('/pricing/'));
        $supportUrl = Config::get('instinctor_support_url', home_url('/support/'));

        EmailService::send('license_delivery', $email, [
            '{customer_name}'    => $customerName,
            '{license_key}'      => $data['license_key'] ?? '',
            '{activation_limit}' => (string) intval($data['activation_limit'] ?? 1),
            '{license_expires}'  => $expiresFormatted,
            '{support_url}'      => $supportUrl,
        ]);
    }

    public function generateLicenseForOrder(int $order_id): void
    {
        
        // Get order
        $order = DB::get_row(DB::prepare(
            "SELECT * FROM {$p}instinctor_orders WHERE id = %d",
            $order_id
        ));
        
        if (!$order) {
            return;
        }
        
        // Get order items
        $items = DB::get_results(DB::prepare(
            "SELECT oi.*, p.product_type, p.meta 
             FROM {$p}instinctor_order_items oi
             JOIN {$p}instinctor_products p ON oi.product_id = p.id
             WHERE oi.order_id = %d",
            $order_id
        ));
        
        foreach ($items as $item) {
            // Check if this is a license product
            $meta = json_decode($item->meta, true) ?: [];
            
            if (($meta['is_license_product'] ?? false) || $item->product_type === 'license') {
                $addon_key = $meta['addon_key'] ?? 'all_access';
                $activation_limit = $meta['activation_limit'] ?? 1;
                $license_days = $meta['license_days'] ?? 365;
                
                // Create license for each quantity
                for ($i = 0; $i < $item->quantity; $i++) {
                    $this->createLicense([
                        'addon' => $addon_key,
                        'customer_id' => $order->customer_id,
                        'customer_email' => $order->email,
                        'order_id' => $order_id,
                        'activation_limit' => $activation_limit,
                        // phpcs:disable WordPress.DateTime.RestrictedFunctions -- date() used for local-timezone formatting, gmdate would force UTC
                        'expires_at' => date('Y-m-d H:i:s', strtotime("+{$license_days} days"))
                        // phpcs:enable WordPress.DateTime.RestrictedFunctions
                    ]);
                }
            }
        }
    }

    /**
     * Normalize site URL for comparison
     */
    private function normalizeSiteUrl(string $url): string
    {
        $url = strtolower(trim($url));
        $url = preg_replace('#^https?://#', '', $url);
        $url = rtrim($url, '/');
        return $url;
    }

    /**
     * REST: Verify license
     */
    public function restVerifyLicense(\WP_REST_Request $request): \WP_REST_Response
    {
        $result = $this->verifyLicense(
            $request->get_param('license_key'),
            $request->get_param('addon'),
            $request->get_param('site_url')
        );
        
        return new \WP_REST_Response($result);
    }

    /**
     * REST: Activate license
     */
    public function restActivateLicense(\WP_REST_Request $request): \WP_REST_Response
    {
        $result = $this->activateLicense(
            $request->get_param('license_key'),
            $request->get_param('addon'),
            $request->get_param('site_url')
        );
        
        return new \WP_REST_Response($result);
    }

    /**
     * REST: Deactivate license
     */
    public function restDeactivateLicense(\WP_REST_Request $request): \WP_REST_Response
    {
        $result = $this->deactivateLicense(
            $request->get_param('license_key'),
            $request->get_param('site_url')
        );
        
        return new \WP_REST_Response($result);
    }

    /**
     * REST: Check license status
     */
    public function restCheckLicense(\WP_REST_Request $request): \WP_REST_Response
    {
        $license = $this->getLicenseByKey($request->get_param('license_key'));
        
        if (!$license) {
            return new \WP_REST_Response([
                'valid' => false,
                'error' => 'invalid_key'
            ]);
        }
        
        $activations = $this->getActivations($license->id);
        
        return new \WP_REST_Response([
            'valid' => $license->status === 'active',
            'addon' => $license->addon,
            'status' => $license->status,
            'expires_at' => $license->expires_at,
            'activation_limit' => $license->activation_limit,
            'activations' => count($activations),
            'sites' => array_map(function($a) {
                return [
                    'url' => $a->site_url,
                    'activated_at' => $a->activated_at
                ];
            }, $activations)
        ]);
    }

    /**
     * AJAX: Verify license (for plugin-side verification)
     */
    public function ajaxVerifyLicense(): void
    {
        check_ajax_referer('instinctor_admin', 'nonce');
        // phpcs:disable WordPress.Security.NonceVerification -- request authenticated via the plugin Session::check/verify HMAC nonce (WPCS cannot recognise it), inputs sanitized
        $license_key = isset($_POST['license_key']) ? sanitize_text_field(wp_unslash($_POST['license_key'])) : '';
        // phpcs:enable WordPress.Security.NonceVerification
        // phpcs:disable WordPress.Security.NonceVerification -- request authenticated via the plugin Session::check/verify HMAC nonce (WPCS cannot recognise it), inputs sanitized
        $addon = isset($_POST['addon']) ? sanitize_text_field(wp_unslash($_POST['addon'])) : '';
        // phpcs:enable WordPress.Security.NonceVerification
        
        if (empty($license_key) || empty($addon)) {
            wp_send_json_error('Missing license key or addon');
        }
        
        $site_url = home_url();
        
        // Call remote API to verify
        $response = wp_remote_post($this->api_endpoint . '/verify', [
            'body' => [
                'license_key' => $license_key,
                'addon' => $addon,
                'site_url' => $site_url
            ],
            'timeout' => 15
        ]);
        
        if (is_wp_error($response)) {
            wp_send_json_error('Could not connect to license server');
        }
        
        $body = json_decode(wp_remote_retrieve_body($response), true);
        
        if ($body['valid'] ?? false) {
            wp_send_json_success($body);
        } else {
            wp_send_json_error($body['message'] ?? 'License verification failed');
        }
    }

    /**
     * Register an AJAX handler for both WordPress and standalone modes.
     * On WordPress: add_action('wp_ajax_' . $action)
     * On standalone: AjaxDispatcher::register($action)
     */
    private function registerAjax(string $action, callable $handler): void
    {
        if (!defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress') {
            add_action('wp_ajax_' . $action, $handler);
        } else {
            \Instinctor\Core\AjaxDispatcher::register($action, $handler);
        }
    }

}
