<?php

namespace PloverCRM\Migrations;

/**
 * Web Push Subscriptions Migration
 * 
 * Creates the table for storing Web Push subscriptions and generates VAPID keys.
 */
class WebPushMigration
{
  private const MIGRATION_VERSION = '1.0.0';
  private const OPTION_KEY = 'plover_crm_web_push_migration_version';
  private const VAPID_OPTION_KEY = 'plover_crm_vapid_keys';

  public static function run(): void
  {
    $current_version = get_option(self::OPTION_KEY, '0');

    if (version_compare($current_version, self::MIGRATION_VERSION, '>=')) {
      return;
    }

    self::createTable();
    self::generateVapidKeys();

    update_option(self::OPTION_KEY, self::MIGRATION_VERSION);
  }

  /**
   * Create web push subscriptions table
   */
  private static function createTable(): void
  {
    global $wpdb;

    $table_name = $wpdb->prefix . 'plover_crm_web_push_subscriptions';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            user_id BIGINT UNSIGNED NOT NULL,
            endpoint TEXT NOT NULL,
            p256dh VARCHAR(255) NOT NULL,
            auth VARCHAR(255) NOT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY idx_user_id (user_id)
        ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);
  }

  /**
   * Generate VAPID keys if they don't exist.
   * 
   * Uses raw OpenSSL as primary method (works on all PHP builds),
   * falls back to the web-push library method if available.
   */
  private static function generateVapidKeys(): void
  {
    $existing = get_option(self::VAPID_OPTION_KEY);

    if ($existing && !empty($existing['publicKey']) && !empty($existing['privateKey'])) {
      return;
    }

    // Try raw OpenSSL first (most compatible)
    $keys = self::generateVapidKeysOpenSSL();

    // Fallback to web-push library
    if (!$keys) {
      try {
        $keys = \Minishlink\WebPush\VAPID::createVapidKeys();
      } catch (\Exception $e) {
        error_log('PloverCRM: Failed to generate VAPID keys: ' . $e->getMessage());
        return;
      }
    }

    if ($keys && !empty($keys['publicKey']) && !empty($keys['privateKey'])) {
      update_option(self::VAPID_OPTION_KEY, [
        'publicKey' => $keys['publicKey'],
        'privateKey' => $keys['privateKey'],
      ]);
    }
  }

  /**
   * Generate VAPID keys using raw OpenSSL functions.
   * This works even when the JWT library's EC key creation fails
   * (e.g. on Local by Flywheel or restricted PHP builds).
   */
  private static function generateVapidKeysOpenSSL(): ?array
  {
    if (!function_exists('openssl_pkey_new')) {
      return null;
    }

    try {
      $key = openssl_pkey_new([
        'curve_name' => 'prime256v1',
        'private_key_type' => OPENSSL_KEYTYPE_EC,
      ]);

      if (!$key) {
        return null;
      }

      $details = openssl_pkey_get_details($key);

      if (!$details || !isset($details['ec'])) {
        return null;
      }

      // Extract the raw private key (32 bytes) and public key point (x, y each 32 bytes)
      $privateKeyRaw = $details['ec']['d'];
      $xRaw = $details['ec']['x'];
      $yRaw = $details['ec']['y'];

      // Pad to 32 bytes each
      $privateKeyRaw = str_pad($privateKeyRaw, 32, "\0", STR_PAD_LEFT);
      $xRaw = str_pad($xRaw, 32, "\0", STR_PAD_LEFT);
      $yRaw = str_pad($yRaw, 32, "\0", STR_PAD_LEFT);

      // Public key is uncompressed point: 0x04 || x || y
      $publicKeyRaw = "\x04" . $xRaw . $yRaw;

      // Encode as base64url (no padding)
      $publicKey = rtrim(strtr(base64_encode($publicKeyRaw), '+/', '-_'), '=');
      $privateKey = rtrim(strtr(base64_encode($privateKeyRaw), '+/', '-_'), '=');

      return [
        'publicKey' => $publicKey,
        'privateKey' => $privateKey,
      ];
    } catch (\Exception $e) {
      return null;
    }
  }
}
