<?php

namespace Drupal\nodehive_entity_access;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessManagerInterface;

/**
 * Manager service for NodeHive Entity Access.
 */
class NodeHiveEntityAccessManager {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The access manager.
   *
   * @var \Drupal\Core\Access\AccessManagerInterface
   */
  protected $accessManager;

  /**
   * The logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected LoggerChannelInterface $logger;

  /**
   * Constructs a NodeHiveEntityAccessManager object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
   *   The access manager.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger channel factory.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    AccessManagerInterface $access_manager,
    LoggerChannelFactoryInterface $logger_factory,
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->accessManager = $access_manager;
    $this->logger = $logger_factory->get('nodehive_entity_access');
  }

  /**
   * Checks if a user has access to an entity.
   *
   * @param int $entity_id
   *   The entity ID.
   * @param string $entity_type_id
   *   The entity type ID.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The user account.
   * @param string $operation
   *   The operation to check (view, update, delete).
   *
   * @return bool
   *   TRUE if the user has access, FALSE otherwise.
   */
  public function userHasEntityAccess($entity_id, $entity_type_id, AccountInterface $account, $operation = 'view'): bool {
    try {
      // Currently we only support node entities.
      if ($entity_type_id !== 'node') {
        return TRUE;
      }

      $node = $this->entityTypeManager->getStorage('node')->load($entity_id);
      if (!$node) {
        $this->logger->notice('Node @nid does not exist, denying access.', ['@nid' => $entity_id]);
        return FALSE;
      }

      // Skip checks for users with administrative permissions.
      if ($account->hasPermission('bypass node access') || $account->hasPermission('administer nodehive')) {
        $this->logger->notice('User @uid has bypass permission, allowing access.', ['@uid' => $account->id()]);
        return TRUE;
      }

      // Log the user we're checking.
      $this->logger->notice('Checking access for user @uid to node @nid.', [
        '@uid' => $account->id(),
        '@nid' => $entity_id,
      ]);

      // Get spaces that contain this node via the node's nodehive_space field.
      if (!$node->hasField('nodehive_space') || $node->get('nodehive_space')->isEmpty()) {
        $this->logger->notice('Node @nid is not in any space, denying access.', ['@nid' => $entity_id]);
        return FALSE;
      }

      $space_ids = array_column($node->get('nodehive_space')->getValue(), 'target_id');

      // Log the spaces this node is in.
      $this->logger->notice('Node @nid is in spaces: @spaces.', [
        '@nid' => $entity_id,
        '@spaces' => implode(', ', $space_ids),
      ]);

      // Find spaces that this user is a member of.
      $space_storage = $this->entityTypeManager->getStorage('nodehive_space');
      $user_space_query = $space_storage->getQuery()
        ->condition('id', $space_ids, 'IN')
        ->condition('editors', $account->id(), 'IN')
        ->accessCheck(FALSE);
      $user_space_ids = $user_space_query->execute();

      // Log if user 3 is being checked.
      if ($account->id() == 3) {
        $this->logger->notice('User 3 is being checked for access to node @nid. User spaces: @spaces.', [
          '@nid' => $entity_id,
          '@spaces' => !empty($user_space_ids) ? implode(', ', $user_space_ids) : 'none',
        ]);
      }

      // Allow access if user is in at least one space containing this node.
      if (!empty($user_space_ids)) {
        $this->logger->notice('User @uid is in spaces with this node: @spaces, allowing access.', [
          '@uid' => $account->id(),
          '@spaces' => implode(', ', $user_space_ids),
        ]);
        return TRUE;
      }

      // Otherwise, deny access.
      $this->logger->notice('User @uid is not in any space with this node, denying access.', ['@uid' => $account->id()]);
      return FALSE;
    }
    catch (\Exception $e) {
      $this->logger->error('Error checking entity access: @message.', ['@message' => $e->getMessage()]);
      return FALSE;
    }
  }

  /**
   * Gets a list of entity IDs that a user can access.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The user account.
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string $operation
   *   The operation to check (view, update, delete).
   *
   * @return array
   *   An array of accessible entity IDs.
   */
  public function getAccessibleEntityIds(AccountInterface $account, $entity_type_id = 'node', $operation = 'view'): array {
    // Currently we only support node entities.
    if ($entity_type_id !== 'node') {
      return [];
    }

    // Get all spaces that this user is a member of.
    $space_storage = $this->entityTypeManager->getStorage('nodehive_space');
    $space_query = $space_storage->getQuery()
      ->condition('editors', $account->id())
      ->accessCheck(FALSE);
    $space_ids = $space_query->execute();

    // Log for user 3.
    if ($account->id() == 3) {
      $this->logger->notice('User 3 is in spaces: @spaces.', [
        '@spaces' => !empty($space_ids) ? implode(', ', $space_ids) : 'none',
      ]);
    }

    // Return empty if user has no spaces and lacks bypass access.
    if (empty($space_ids) && !$account->hasPermission('bypass node access')) {
      return [];
    }

    // Get all node IDs from user's spaces.
    $node_ids = [];
    if (!empty($space_ids)) {
      $node_ids = array_values(
        $this->entityTypeManager->getStorage('node')->getQuery()
          ->accessCheck(FALSE)
          ->condition('nodehive_space', $space_ids, 'IN')
          ->execute()
      );
    }

    // If user has bypass access, get all nodes instead.
    if ($account->hasPermission('bypass node access')) {
      $node_ids = $this->getAllNodeIds();
    }

    // Log for user 3.
    if ($account->id() == 3) {
      $this->logger->notice('User 3 has access to nodes: @nodes.', [
        '@nodes' => !empty($node_ids) ? implode(', ', $node_ids) : 'none',
      ]);
    }

    return $node_ids;
  }

  /**
   * Gets all node IDs in the system.
   *
   * @return array
   *   An array of all node IDs.
   */
  public function getAllNodeIds(): array {
    $node_storage = $this->entityTypeManager->getStorage('node');
    $query = $node_storage->getQuery()->accessCheck(FALSE);
    return $query->execute();
  }

}
