<?php

namespace PloverCRM\Modules\Integrations\Services;

/**
 * Webhook Service
 * 
 * Manages incoming webhook endpoints.
 * Each webhook has a unique token used in the URL for receiving data.
 */
class WebhookService
{
  const TABLE_NAME = 'plover_crm_webhooks';

  /**
   * Ensure the webhooks table exists
   */
  public static function ensureTableExists(): void
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;

    if ($wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table) {
      self::createTable();
    }
  }

  /**
   * Create the webhooks table
   */
  public static function createTable(): void
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table (
      id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      name varchar(255) NOT NULL,
      token varchar(64) NOT NULL,
      last_payload longtext DEFAULT NULL,
      status varchar(20) NOT NULL DEFAULT 'active',
      created_at bigint(20) unsigned NOT NULL DEFAULT 0,
      updated_at bigint(20) unsigned NOT NULL DEFAULT 0,
      PRIMARY KEY (id),
      UNIQUE KEY token (token),
      KEY status (status)
    ) $charset_collate;";

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

  /**
   * Get all webhooks
   */
  public static function getAll(): array
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;
    return $wpdb->get_results("SELECT * FROM $table ORDER BY id DESC", ARRAY_A) ?: [];
  }

  /**
   * Get a webhook by ID
   */
  public static function getById(int $id): ?array
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;
    $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id), ARRAY_A);
    return $row ?: null;
  }

  /**
   * Get a webhook by token
   */
  public static function getByToken(string $token): ?array
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;
    $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE token = %s", $token), ARRAY_A);
    return $row ?: null;
  }

  /**
   * Create a new webhook
   */
  public static function create(string $name): ?array
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;

    $token = self::generateToken();
    $now = time();

    $wpdb->insert($table, [
      'name' => sanitize_text_field($name),
      'token' => $token,
      'status' => 'active',
      'created_at' => $now,
      'updated_at' => $now,
    ]);

    $id = $wpdb->insert_id;
    if (!$id) {
      return null;
    }

    return self::getById($id);
  }

  /**
   * Update a webhook
   */
  public static function update(int $id, array $data): bool
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;

    $allowed = ['name', 'status'];
    $updateData = [];

    foreach ($allowed as $key) {
      if (isset($data[$key])) {
        $updateData[$key] = sanitize_text_field($data[$key]);
      }
    }

    if (empty($updateData)) {
      return false;
    }

    $updateData['updated_at'] = time();

    return (bool) $wpdb->update($table, $updateData, ['id' => $id]);
  }

  /**
   * Delete a webhook
   */
  public static function delete(int $id): bool
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;
    return (bool) $wpdb->delete($table, ['id' => $id], ['%d']);
  }

  /**
   * Store the last received payload for field discovery
   * Merges with previously discovered fields so no keys are lost
   */
  public static function storeLastPayload(int $id, array $payload): void
  {
    global $wpdb;
    $table = $wpdb->prefix . self::TABLE_NAME;

    // Get existing payload and merge keys
    $existing = self::getById($id);
    if ($existing && !empty($existing['last_payload'])) {
      $existingPayload = json_decode($existing['last_payload'], true);
      if (is_array($existingPayload)) {
        // Deep merge — new payload values take priority, but old keys are preserved
        $payload = self::deepMerge($existingPayload, $payload);
      }
    }

    $wpdb->update($table, [
      'last_payload' => json_encode($payload),
      'updated_at' => time(),
    ], ['id' => $id]);
  }

  /**
   * Deep merge two arrays — $b values override $a, but $a keys are preserved
   */
  private static function deepMerge(array $a, array $b): array
  {
    foreach ($b as $key => $value) {
      if (is_array($value) && isset($a[$key]) && is_array($a[$key]) && !self::isIndexedArray($value)) {
        $a[$key] = self::deepMerge($a[$key], $value);
      } else {
        $a[$key] = $value;
      }
    }
    return $a;
  }

  /**
   * Get the flattened field keys from the last payload
   */
  public static function getDiscoveredFields(int $id): array
  {
    $webhook = self::getById($id);
    if (!$webhook || empty($webhook['last_payload'])) {
      return [];
    }

    $payload = json_decode($webhook['last_payload'], true);
    if (!is_array($payload)) {
      return [];
    }

    $fields = [];
    self::flattenKeys($payload, '', $fields);
    return $fields;
  }

  /**
   * Recursively flatten nested array keys into dot notation
   */
  private static function flattenKeys(array $data, string $prefix, array &$fields): void
  {
    foreach ($data as $key => $value) {
      $fullKey = $prefix === '' ? (string) $key : $prefix . '.' . $key;

      if (is_array($value) && !self::isIndexedArray($value)) {
        // Nested associative array — recurse
        self::flattenKeys($value, $fullKey, $fields);
      } else {
        // Leaf value or indexed array — add as field
        // Show dot-notation key in label so users can identify the exact path
        $label = $fullKey;
        $fields[] = [
          'key' => $fullKey,
          'label' => $label,
          'type' => self::guessFieldType($value),
        ];
      }
    }
  }

  /**
   * Check if an array is numerically indexed (list) vs associative (object)
   */
  private static function isIndexedArray(array $arr): bool
  {
    return array_keys($arr) === range(0, count($arr) - 1);
  }

  /**
   * Guess field type from value
   */
  private static function guessFieldType($value): string
  {
    if (is_bool($value)) return 'checkbox';
    if (is_numeric($value)) return 'number';
    if (is_string($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) return 'email';
    if (is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) return 'url';
    return 'text';
  }

  /**
   * Generate a unique webhook token
   */
  private static function generateToken(): string
  {
    return bin2hex(random_bytes(16));
  }

  /**
   * Get the public webhook URL for a token
   */
  public static function getWebhookUrl(string $token): string
  {
    return rest_url('plover-crm/v1/webhook/incoming/' . $token);
  }
}
