<?php

declare(strict_types=1);

namespace Drupal\flowdrop_trigger\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\flowdrop_trigger\Enum\EventCategory;

/**
 * Service for filtering events based on configuration.
 *
 * Checks category-specific settings to determine whether events, entity types,
 * bundles, form IDs, and user roles are allowed by the trigger system.
 *
 * @internal
 *   This class is not part of the public API and may change without notice.
 */
class EventFilterService {

  /**
   * Constructs an EventFilterService.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory.
   * @param \Drupal\flowdrop_trigger\Service\EventTypePluginManager $eventTypeManager
   *   The event type plugin manager.
   */
  public function __construct(
    protected readonly ConfigFactoryInterface $configFactory,
    protected readonly EventTypePluginManager $eventTypeManager,
  ) {}

  /**
   * Get the category config name for an event type.
   *
   * @param string $eventType
   *   The event type to check.
   *
   * @return string
   *   The config name (e.g., "flowdrop_trigger.settings.entity").
   */
  public function getCategoryConfigName(string $eventType): string {
    $definition = $this->eventTypeManager->getDefinition($eventType, FALSE);
    $category = EventCategory::toString($definition["category"] ?? "entity");

    return sprintf("flowdrop_trigger.settings.%s", $category);
  }

  /**
   * Check if an event type is allowed by configuration.
   *
   * Checks the category-specific settings for the event type:
   * - Category must be enabled
   * - Event type must be in allowed list (if restrictions are set)
   *
   * @param string $eventType
   *   The event type to check.
   *
   * @return bool
   *   TRUE if the event type is allowed, FALSE otherwise.
   */
  public function isEventTypeAllowed(string $eventType): bool {
    $configName = $this->getCategoryConfigName($eventType);
    $config = $this->configFactory->get($configName);

    // Check if category is explicitly enabled.
    // Default to FALSE (disabled) if not explicitly set to true.
    // This ensures triggers only fire when intentionally configured.
    if ($config->get("enabled") !== TRUE) {
      return FALSE;
    }

    // Check event type restrictions.
    $allowedEventTypes = $config->get("allowed_event_types") ?? [];

    // If no restrictions are configured, allow all event types in this
    // category.
    if (empty($allowedEventTypes)) {
      return TRUE;
    }

    // Check if the event type is in the allowed list.
    return in_array($eventType, $allowedEventTypes, TRUE);
  }

  /**
   * Check if an event type is an entity-related event.
   *
   * Entity events start with "entity." prefix.
   *
   * @param string $eventType
   *   The event type to check.
   *
   * @return bool
   *   TRUE if this is an entity event, FALSE otherwise.
   */
  public function isEntityEvent(string $eventType): bool {
    return str_starts_with($eventType, "entity.");
  }

  /**
   * Check if an event type is a user-related event.
   *
   * User events start with "user." prefix.
   *
   * @param string $eventType
   *   The event type to check.
   *
   * @return bool
   *   TRUE if this is a user event, FALSE otherwise.
   */
  public function isUserEvent(string $eventType): bool {
    return str_starts_with($eventType, "user.");
  }

  /**
   * Check if an event type is a cron-related event.
   *
   * Cron events start with "cron." prefix.
   *
   * @param string $eventType
   *   The event type to check.
   *
   * @return bool
   *   TRUE if this is a cron event, FALSE otherwise.
   */
  public function isCronEvent(string $eventType): bool {
    return str_starts_with($eventType, "cron.");
  }

  /**
   * Check if an entity type is allowed by configuration.
   *
   * Checks the entity category settings for entity type restrictions.
   *
   * @param string $entityType
   *   The entity type to check.
   *
   * @return bool
   *   TRUE if the entity type is allowed, FALSE otherwise.
   */
  public function isEntityTypeAllowed(string $entityType): bool {
    $config = $this->configFactory->get("flowdrop_trigger.settings.entity");
    $allowedEntityTypes = $config->get("allowed_entity_types") ?? [];

    // If no restrictions are configured, allow all entity types.
    if (empty($allowedEntityTypes)) {
      return TRUE;
    }

    // Check if the entity type is in the allowed list.
    return in_array($entityType, $allowedEntityTypes, TRUE);
  }

  /**
   * Check if a bundle is allowed by configuration.
   *
   * Checks the entity category settings for bundle restrictions.
   * Bundle format is "entity_type:bundle" (e.g., "node:article").
   *
   * @param string $entityType
   *   The entity type.
   * @param string $bundle
   *   The bundle to check.
   *
   * @return bool
   *   TRUE if the bundle is allowed, FALSE otherwise.
   */
  public function isBundleAllowed(string $entityType, string $bundle): bool {
    $config = $this->configFactory->get("flowdrop_trigger.settings.entity");
    $allowedBundles = $config->get("allowed_bundles") ?? [];

    // If no restrictions are configured, allow all bundles.
    if (empty($allowedBundles)) {
      return TRUE;
    }

    // Build the bundle key in the format "entity_type:bundle".
    $bundleKey = sprintf("%s:%s", $entityType, $bundle);

    // Check if the bundle is in the allowed list.
    return in_array($bundleKey, $allowedBundles, TRUE);
  }

  /**
   * Check if a form ID is allowed by configuration.
   *
   * Checks the form category settings for form ID restrictions.
   * Supports wildcards in allowed/excluded form IDs.
   *
   * @param string $formId
   *   The form ID to check.
   *
   * @return bool
   *   TRUE if the form ID is allowed, FALSE otherwise.
   */
  public function isFormIdAllowed(string $formId): bool {
    $config = $this->configFactory->get("flowdrop_trigger.settings.form");

    // Check excluded form IDs first (takes precedence).
    $excludedFormIds = $config->get("excluded_form_ids") ?? [];
    foreach ($excludedFormIds as $pattern) {
      if ($this->matchWildcard($formId, $pattern)) {
        return FALSE;
      }
    }

    // Check allowed form IDs.
    $allowedFormIds = $config->get("allowed_form_ids") ?? [];

    // If no restrictions are configured, allow all form IDs.
    if (empty($allowedFormIds)) {
      return TRUE;
    }

    // Check if the form ID matches any allowed pattern.
    foreach ($allowedFormIds as $pattern) {
      if ($this->matchWildcard($formId, $pattern)) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Check if a string matches a wildcard pattern.
   *
   * Supports * as a wildcard character.
   *
   * @param string $string
   *   The string to check.
   * @param string $pattern
   *   The pattern (may contain * wildcards).
   *
   * @return bool
   *   TRUE if the string matches the pattern.
   */
  public function matchWildcard(string $string, string $pattern): bool {
    // Convert wildcard pattern to regex.
    // First escape regex special characters with preg_quote.
    // Then replace the escaped wildcards (\* and \?) with their regex
    // equivalents (.* and .). Note: preg_quote escapes * to \* and ? to \?.
    $regex = str_replace(["\\*", "\\?"], [".*", "."], preg_quote($pattern, "/"));
    return (bool) preg_match("/^{$regex}$/", $string);
  }

  /**
   * Check if a user role is allowed by configuration.
   *
   * Checks the user category settings for role restrictions.
   *
   * @param array<string> $roles
   *   The user roles to check.
   *
   * @return bool
   *   TRUE if at least one role is allowed, FALSE otherwise.
   */
  public function isUserRoleAllowed(array $roles): bool {
    $config = $this->configFactory->get("flowdrop_trigger.settings.user");

    // Check excluded roles first (takes precedence).
    $excludedRoles = $config->get("excluded_roles") ?? [];
    foreach ($roles as $role) {
      if (in_array($role, $excludedRoles, TRUE)) {
        return FALSE;
      }
    }

    // Check allowed roles.
    $allowedRoles = $config->get("allowed_roles") ?? [];

    // If no restrictions are configured, allow all roles.
    if (empty($allowedRoles)) {
      return TRUE;
    }

    // Check if at least one role is in the allowed list.
    foreach ($roles as $role) {
      if (in_array($role, $allowedRoles, TRUE)) {
        return TRUE;
      }
    }

    return FALSE;
  }

}
