<?php

/**
 * @file
 * Provides NodeHive entity access control.
 */

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_entity_type_alter().
 */
function nodehive_entity_access_entity_type_alter(array &$entity_types) {
  // Apply our custom access control handler to node entities.
  if (isset($entity_types['node'])) {
    $entity_types['node']->setHandlerClass('access', 'Drupal\nodehive_entity_access\NodeHiveEntityAccessControlHandler');
  }
}

/**
 * Implements hook_views_query_alter().
 */
function nodehive_entity_access_views_query_alter(ViewExecutable $view, $query) {
  // Only apply to node views.
  if ($view->getBaseEntityType()->id() !== 'node') {
    return;
  }

  // Get current user.
  $account = \Drupal::currentUser();

  // Skip for users with administrative access.
  if ($account->hasPermission('bypass node access') || $account->hasPermission('administer nodehive')) {
    return;
  }

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

  // Specifically log for user 3.
  if ($account->id() == 3) {
    \Drupal::logger('nodehive_entity_access')->notice('User 3 view query: User is in spaces: @spaces.', [
      '@spaces' => !empty($space_ids) ? implode(', ', $space_ids) : 'none',
    ]);
  }

  // If user has no spaces, don't show any nodes.
  if (empty($space_ids)) {
    // Force no results by adding an impossible condition.
    // In Views, usually the node ID field is 'nid'.
    $query->addWhere('nodehive_entity_access', 'node_field_data.nid', -1, '=');
    return;
  }

  // Load all spaces the user is part of.
  $spaces = $space_storage->loadMultiple($space_ids);

  // Extract node IDs from all user's spaces.
  $accessible_nids = [];
  foreach ($spaces as $space) {
    if ($space->hasField('content')) {
      $space_nodes = $space->get('content')->getValue();
      foreach ($space_nodes as $node_reference) {
        $accessible_nids[] = $node_reference['target_id'];
      }
    }
  }

  // Specifically log for user 3.
  if ($account->id() == 3) {
    \Drupal::logger('nodehive_entity_access')->notice('User 3 view query: User has access to nodes: @nodes.', [
      '@nodes' => !empty($accessible_nids) ? implode(', ', $accessible_nids) : 'none',
    ]);
  }

  // If there are no accessible nodes, apply a condition that will never match.
  if (empty($accessible_nids)) {
    $query->addWhere('nodehive_entity_access', 'node_field_data.nid', -1, '=');
    return;
  }

  // Add condition to only show nodes from user's spaces.
  $query->addWhere('nodehive_entity_access', 'node_field_data.nid', $accessible_nids, 'IN');

  \Drupal::logger('nodehive_entity_access')->notice('Modified views query for user @uid to only show nodes from spaces: @spaces.', [
    '@uid' => $account->id(),
    '@spaces' => implode(', ', $space_ids),
  ]);
}

/**
 * Implements hook_query_TAG_alter() for 'entity_query'.
 */
function nodehive_entity_access_query_entity_query_alter(AlterableInterface $query) {
  // Only apply to node entity queries.
  if (!($query instanceof SelectInterface)) {
    return;
  }

  // Get current user.
  $account = \Drupal::currentUser();

  // Skip for users with administrative access.
  if ($account->hasPermission('bypass node access') || $account->hasPermission('administer nodehive')) {
    return;
  }

  // Check if this is a node query.
  $tables = $query->getTables();
  $is_node_query = FALSE;
  $node_table_alias = '';

  foreach ($tables as $table_info) {
    if (is_string($table_info['table']) && ($table_info['table'] === 'node' || $table_info['table'] === 'node_field_data')) {
      $is_node_query = TRUE;
      $node_table_alias = $table_info['alias'];
      break;
    }
  }

  if (!$is_node_query) {
    return;
  }

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

  // Specifically log for user 3.
  if ($account->id() == 3) {
    \Drupal::logger('nodehive_entity_access')->notice('User 3 entity query: User is in spaces: @spaces.', [
      '@spaces' => !empty($space_ids) ? implode(', ', $space_ids) : 'none',
    ]);
  }

  // If user has no spaces, don't show any nodes.
  if (empty($space_ids)) {
    // Force no results by adding an impossible condition.
    $query->condition($node_table_alias . '.nid', -1);
    return;
  }

  // Load all spaces the user is part of.
  $spaces = $space_storage->loadMultiple($space_ids);

  // Extract node IDs from all user's spaces.
  $accessible_nids = [];
  foreach ($spaces as $space) {
    if ($space->hasField('content')) {
      $space_nodes = $space->get('content')->getValue();
      foreach ($space_nodes as $node_reference) {
        $accessible_nids[] = $node_reference['target_id'];
      }
    }
  }

  // Specifically log for user 3.
  if ($account->id() == 3) {
    \Drupal::logger('nodehive_entity_access')->notice('User 3 entity query: User has access to nodes: @nodes.', [
      '@nodes' => !empty($accessible_nids) ? implode(', ', $accessible_nids) : 'none',
    ]);
  }

  // If there are no accessible nodes, apply a condition that will never match.
  if (empty($accessible_nids)) {
    $query->condition($node_table_alias . '.nid', -1);
    return;
  }

  // Add condition to only show nodes from user's spaces.
  $query->condition($node_table_alias . '.nid', $accessible_nids, 'IN');

  \Drupal::logger('nodehive_entity_access')->notice('Modified entity query for user @uid to only show nodes from spaces: @spaces.', [
    '@uid' => $account->id(),
    '@spaces' => implode(', ', $space_ids),
  ]);
}

/**
 * Implements hook_entity_access().
 */
function nodehive_entity_access_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
  // We only want to handle node entities in this module.
  if ($entity->getEntityTypeId() !== 'node') {
    return AccessResult::neutral();
  }

  // Log access attempts.
  \Drupal::logger('nodehive_entity_access')->notice('Checking entity access for @type @id, operation: @op, user: @uid.', [
    '@type' => $entity->getEntityTypeId(),
    '@id' => $entity->id(),
    '@op' => $operation,
    '@uid' => $account->id(),
  ]);

  // Skip checks for users with administrative permissions.
  if ($account->hasPermission('bypass node access') || $account->hasPermission('administer nodehive')) {
    \Drupal::logger('nodehive_entity_access')->notice('User has bypass permission, allowing access.');
    return AccessResult::allowed()->setCacheMaxAge(0);
  }

  // Get the node ID.
  $nid = $entity->id();

  // Get spaces that contain this node.
  $space_storage = \Drupal::entityTypeManager()->getStorage('nodehive_space');
  $space_query = $space_storage->getQuery()
    ->condition('content', $nid, 'IN')
    ->accessCheck(FALSE);
  $space_ids = $space_query->execute();

  // If this node isn't in any space, deny access.
  if (empty($space_ids)) {
    \Drupal::logger('nodehive_entity_access')->notice('Node @nid is not in any space, denying access.', ['@nid' => $nid]);
    return AccessResult::forbidden()->setCacheMaxAge(0);
  }

  \Drupal::logger('nodehive_entity_access')->notice('Node @nid is in spaces: @spaces.', [
    '@nid' => $nid,
    '@spaces' => implode(', ', $space_ids),
  ]);

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

  // If the user is a member of at least one space that contains this node, allow access.
  if (!empty($user_space_ids)) {
    \Drupal::logger('nodehive_entity_access')->notice('User @uid is in spaces with this node: @spaces, allowing access.', [
      '@uid' => $account->id(),
      '@spaces' => implode(', ', $user_space_ids),
    ]);
    return AccessResult::allowed()->setCacheMaxAge(0);
  }

  // Otherwise, deny access.
  \Drupal::logger('nodehive_entity_access')->notice('User @uid is not in any space with this node, denying access.', ['@uid' => $account->id()]);
  return AccessResult::forbidden()->setCacheMaxAge(0);
}
