<?php

/**
 * @file
 * Install, update and uninstall functions for the farm_api module.
 */

use Drupal\consumers\Entity\Consumer;

/**
 * Implements hook_install().
 */
function farm_api_install() {

  // Allow JSON:API write operations.
  \Drupal::configFactory()->getEditable('jsonapi.settings')->set('read_only', FALSE)->save();

  // Change the base JSON:API path to /api.
  \Drupal::configFactory()->getEditable('jsonapi_extras.settings')->set('path_prefix', 'api')->save();

  // Load the simple_oauth module settings.
  $simple_oauth_settings = \Drupal::configFactory()->getEditable('simple_oauth.settings');

  // Increase access token expiration time to 1 hour.
  $simple_oauth_settings->set('access_token_expiration', 3600);

  // Explicitly set the public/private key path.
  $simple_oauth_settings->set('public_key', '../keys/public.key');
  $simple_oauth_settings->set('private_key', '../keys/private.key');

  // Save simple_oauth settings.
  $simple_oauth_settings->save();

  // Generate key files for simple_oauth module.
  // Code adapted from the contenta_jsonapi profile install tasks:
  // https://github.com/contentacms/contenta_jsonapi/blob/8.x-3.x/contenta_jsonapi.profile
  // Build all the dependencies manually to avoid having to rely on the
  // container to be ready.
  $dir_name = 'keys';
  /** @var \Drupal\simple_oauth\Service\KeyGeneratorService $key_gen */
  $key_gen = \Drupal::service('simple_oauth.key.generator');
  /** @var \Drupal\simple_oauth\Service\Filesystem\FileSystemChecker $file_system_checker */
  $file_system_checker = \Drupal::service('simple_oauth.filesystem_checker');
  /** @var \Drupal\Core\File\FileSystem $file_system */
  $file_system = \Drupal::service('file_system');

  // Save keys in the "keys" directory outside of the webroot.
  $relative_path = DRUPAL_ROOT . '/../' . $dir_name;
  if (!$file_system_checker->isDirectory($relative_path)) {
    $file_system->mkdir($relative_path);
  }
  $keys_path = $file_system->realpath($relative_path);
  $pub_filename = sprintf('%s/public.key', $keys_path);
  $pri_filename = sprintf('%s/private.key', $keys_path);

  // Attempt to create keys if they don't exist.
  if (!$file_system_checker->fileExist($pub_filename) || !$file_system_checker->fileExist($pri_filename)) {
    try {
      $key_gen->generateKeys($keys_path);
    }
    catch (\Exception $e) {
      watchdog_exception('farm_api', $e);
    }
  }

  // Delete the "Default Consumer" created by the consumers module.
  $default_consumer = \Drupal::entityTypeManager()->getStorage('consumer')
    ->loadByProperties(['label' => 'Default Consumer']);
  if (!empty($default_consumer)) {
    $default_consumer = reset($default_consumer);
    $default_consumer->delete();
  }

  // Create a "Farm default" consumer.
  $base_url = \Drupal::service('router.request_context')->getCompleteBaseUrl();
  $farm_consumer = Consumer::create([
    'label' => 'Farm default',
    'client_id' => 'farm',
    'redirect' => $base_url,
    'is_default' => TRUE,
    'owner_id' => '',
    'secret' => NULL,
    'confidential' => FALSE,
    'third_party' => FALSE,
    'grant_user_access' => TRUE,
    'limit_user_access' => TRUE,
    'limit_requested_access' => FALSE,
  ]);
  $farm_consumer->save();

}

/**
 * Implements hook_uninstall().
 */
function farm_api_uninstall() {

  // Load the default farm consumer.
  $consumers = \Drupal::entityTypeManager()->getStorage('consumer')
    ->loadByProperties(['client_id' => 'farm']);

  // If found, delete the consumer.
  if (!empty($consumers)) {
    $farm_consumer = reset($consumers);
    $farm_consumer->delete();
  }
}

/**
 * Implements hook_update_dependencies().
 */
function farm_api_update_dependencies() {

  // The Consumers module added a client_id base field in version 1.14, which
  // replaces our custom client_id field. We need to migrate the client IDs
  // from our old field to the new field, which requires running our own
  // update hooks before and after those of the Consumers module.
  $dependencies['consumers'][8108] = [
    'farm_api' => 9000,
  ];
  $dependencies['farm_api'][9001] = [
    'consumers' => 8109,
  ];

  return $dependencies;
}

/**
 * Save client_id values and uninstall farmOS client_id field.
 */
function farm_api_update_9000(&$sandbox) {

  // This update hook must run before consumers_update_8108().
  // @see farm_api_update_dependencies().

  // Get all client_id values from the database.
  $client_ids = [];
  $result = \Drupal::database()->query('SELECT entity_id, client_id_value FROM {consumer__client_id}')->fetchAll();
  foreach ($result as $record) {
    $client_ids[$record->entity_id] = $record->client_id_value;
  }

  // Save the client_id values to Drupal state.
  if (!empty($client_ids)) {
    \Drupal::state()->set('farm_api_consumer_client_ids', $client_ids);
  }

  // Uninstall the client_id field.
  /** @var \Drupal\entity\BundleFieldDefinition $definition */
  $definition = \Drupal::service('farm_field.factory')->bundleFieldDefinition(['type' => 'string']);
  $definition->setTargetEntityTypeId('consumer');
  $definition->setName('client_id');
  \Drupal::entityDefinitionUpdateManager()->uninstallFieldStorageDefinition($definition);
}

/**
 * Restore client_id values saved from farmOS client_id field.
 */
function farm_api_update_9001(&$sandbox) {

  // This update hook must run after consumers_update_8109().
  // @see farm_api_update_dependencies().

  // Load saved client_id values from old client_id field.
  $client_ids = \Drupal::state()->get('farm_api_consumer_client_ids', []);
  if (empty($client_ids)) {
    return;
  }

  // Save client_id values to new client_id field.
  // Use SQL update instead of entity save so tokens do not become invalidated.
  $database = \Drupal::database();
  foreach ($client_ids as $consumer_id => $client_id) {
    $database->update('consumer_field_data')
      ->fields(['client_id' => $client_id])
      ->condition('id', $consumer_id)
      ->execute();
  }
}
