<?php

namespace Drupal\eb\Service;

use Drupal\Core\Config\ConfigFactoryInterface;

/**
 * Provides security services for definition export/import.
 *
 * Handles sanitization of exported data and optional HMAC signing for
 * verification of imported definitions.
 */
class ExportSecurityService {

  /**
   * Internal fields that should be removed from exports.
   *
   * These fields contain tracking/internal data that shouldn't be shared.
   *
   * @var array<string>
   */
  protected const INTERNAL_FIELDS = [
    'uid',
    'project',
    'application_status',
    'reviewer_uid',
    'reviewed_timestamp',
    'review_notes',
    'rejection_reason',
    'created',
    'changed',
    'langcode',
    'uuid',
  ];

  /**
   * Constructor.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory.
   */
  public function __construct(
    protected ConfigFactoryInterface $configFactory,
  ) {}

  /**
   * Sanitizes definition data for safe export.
   *
   * Removes internal tracking fields that shouldn't be shared.
   *
   * @param array<string, mixed> $data
   *   The definition data array from toExportArray().
   *
   * @return array<string, mixed>
   *   Sanitized data safe for export.
   */
  public function sanitizeForExport(array $data): array {
    // Remove internal fields from root.
    foreach (self::INTERNAL_FIELDS as $field) {
      unset($data[$field]);
    }

    return $data;
  }

  /**
   * Signs exported YAML content with HMAC.
   *
   * Optionally signs the export content for later verification. The signing
   * key must be configured in eb.settings.
   *
   * @param string $yamlContent
   *   The YAML content to sign.
   *
   * @return string
   *   YAML content with signature header prepended if signing is enabled,
   *   or original content if no signing key configured.
   */
  public function signExport(string $yamlContent): string {
    $signingKey = $this->getSigningKey();
    if (empty($signingKey)) {
      return $yamlContent;
    }

    $signature = hash_hmac('sha256', $yamlContent, $signingKey);
    $timestamp = date('c');

    return "# SIGNATURE: {$signature}\n# SIGNED_AT: {$timestamp}\n{$yamlContent}";
  }

  /**
   * Verifies a signed YAML export.
   *
   * Extracts and verifies the HMAC signature from signed YAML content.
   *
   * @param string $yamlContent
   *   The potentially signed YAML content.
   *
   * @return array{verified: bool, content: string, signed: bool}
   *   Result array with:
   *   - verified: TRUE if signature is valid or content is unsigned
   *   - content: YAML content without signature headers
   *   - signed: TRUE if content had a signature
   */
  public function verifySignature(string $yamlContent): array {
    $pattern = '/^# SIGNATURE: ([a-f0-9]+)\n# SIGNED_AT: [^\n]+\n/';

    if (!preg_match($pattern, $yamlContent, $matches)) {
      // Not signed, return as-is.
      return [
        'verified' => TRUE,
        'content' => $yamlContent,
        'signed' => FALSE,
      ];
    }

    $providedSignature = $matches[1];
    $content = preg_replace($pattern, '', $yamlContent);

    $signingKey = $this->getSigningKey();
    if (empty($signingKey)) {
      // No key configured, cannot verify but content was signed.
      return [
        'verified' => FALSE,
        'content' => $content,
        'signed' => TRUE,
      ];
    }

    $expectedSignature = hash_hmac('sha256', $content, $signingKey);
    $verified = hash_equals($expectedSignature, $providedSignature);

    return [
      'verified' => $verified,
      'content' => $content,
      'signed' => TRUE,
    ];
  }

  /**
   * Gets the signing key from configuration.
   *
   * @return string|null
   *   The configured signing key or NULL if not set.
   */
  protected function getSigningKey(): ?string {
    $config = $this->configFactory->get('eb.settings');
    return $config->get('export_signing_key');
  }

  /**
   * Checks if export signing is enabled.
   *
   * @return bool
   *   TRUE if a signing key is configured.
   */
  public function isSigningEnabled(): bool {
    return !empty($this->getSigningKey());
  }

}
