<?php

namespace Drupal\autoslave\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Core\Database\Database;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Controller for AutoSlave dashboard and monitoring.
 */
class AutoslaveDashboardController extends ControllerBase {

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * Constructs a AutoslaveDashboardController object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
    $this->configFactory = $config_factory;
    $this->moduleHandler = $module_handler;
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('module_handler'),
      $container->get('current_user')
    );
  }

  /**
   * AJAX callback to trigger autoslave dashboard cron.
   *
   * Page callback providing link on autoslave_dashboard_page to trigger
   * autoslave_dashboard_cron. Path: admin/config/system/autoslave/cron.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with status.
   */
  public function autoslaveCron() {
    self::collectDashboardStatistics();
    // Nice wait animation on interface.
    sleep(1);
    return new JsonResponse(['status' => TRUE, 'result' => 'ok']);
  }

  /**
   * Implements the dashboard page rendering.
   */
  public function autoslaveDashboard() {
    // Check access permissions.
    if (!$this->currentUser->hasPermission('administer site configuration') &&
        !$this->currentUser->hasPermission('autoslave dashboard hash key')) {
      throw new AccessDeniedHttpException();
    }

    if (!autoslave_is_driver_loaded()) {
      \Drupal::messenger()->addMessage('AutoSlave driver is not loaded', 'warning');
    }

    // Pass $asd_full_link to the theme autoslave_dashboard for autoslave_dashboard.tpl.php.
    $asd_formatted_hashlink = NULL;
    // Do master processing only once.
    $master_host_port = [];
    if ($this->currentUser->hasPermission('administer site configuration')) {
      $config = $this->configFactory->get('autoslave.settings');
      $asd_hashkey = self::getHashFromHostname();
      // Use trusted request object to build URL safely.
      $request = \Drupal::request();
      $base_url = $request->getSchemeAndHttpHost();
      $url_key = $config->get('autoslave_dashboard_url_key', 'hash');
      $asd_link = $base_url . '/autoslave/dashboard?' . $url_key . '=' . $asd_hashkey;
      $asd_link_text = $this->t('You can use @link to view this dashboard without being logged in.', [
        '@link' => $asd_link,
      ]);
      $options['attributes']['title'] = $asd_link_text;
      $options['query'][$config->get('autoslave_dashboard_url_key', 'hash')] = $asd_hashkey;
      $asd_formatted_hashlink = Link::fromTextAndUrl($asd_link_text, Url::fromRoute('autoslave.dashboard', $options))->toString();
    }

    // Do processing for json, output as a string, pass it to the theme variables.
    $array_of_servers = [];
    // To be used for the autoslave_server_stats array processing.
    $array_for_json = [];
    // To be used for the autoslave_slave_status array processing.
    $array_slave_status = [];
    $result_num_servers = \Drupal::database()->select('autoslave_server_stats', 'a')
      ->fields('a', ['server_num'])
      ->distinct()
      ->execute()
      ->fetchAll();
    $num_servers = count($result_num_servers);
    foreach ($result_num_servers as $srvnum_server) {
      // Start the autoslave_server_stats json_data array processing.
      $srvnum = $srvnum_server->server_num;
      $array_of_servers[] = $srvnum;
      $data = [];
      $config = $this->configFactory->get('autoslave.settings');
      $number_of_deltas = (int) ($config->get('autoslave_dashboard_deltas', 30) + 1);
      if (!is_numeric($number_of_deltas)) {
        // Number of bars = $number_of_deltas -1.
        $number_of_deltas = 31;
      }
      $countResults = \Drupal::database()->select('autoslave_server_stats', 'a')
        ->fields('a', ['server_num', 'n_select', 'n_insert', 'n_update', 'ts'])
        ->condition('server_num', $srvnum, '=')
        ->execute()
        ->fetchAll();
      $total_number_of_stats = count($countResults);
      $start_range = $total_number_of_stats - $number_of_deltas;
      if ($start_range < 0) {
        $start_range = 0;
      }
      $results = \Drupal::database()->select('autoslave_server_stats', 'a')
        ->fields('a', ['server_num', 'n_select', 'n_insert', 'n_update', 'ts'])
        ->condition('server_num', $srvnum, '=')
        ->range($start_range, $total_number_of_stats)
        ->orderBy('ts', 'ASC')
        ->execute()
        ->fetchAll();
      $prev_select = NULL;
      $prev_insert = NULL;
      $prev_update = NULL;
      foreach ($results as $row) {
        if ($prev_select === NULL) {
          $prev_select = $row->n_select;
          $prev_insert = $row->n_insert;
          $prev_update = $row->n_update;
          continue;
        }
        $array_for_json[$srvnum][] = [
          $row->n_select - $prev_select,
          $row->n_insert - $prev_insert,
          $row->n_update - $prev_update,
        ];
        $prev_select = $row->n_select;
        $prev_insert = $row->n_insert;
        $prev_update = $row->n_update;
      }
      // End of the autoslave_server_stats json_data array processing.
      // Start the autoslave_slave_status status_data array.
      $results = \Drupal::database()->select('autoslave_slave_status', 'a')
        ->fields('a', ['server_num',
          'io_state',
          'master_host',
          'master_port',
          'master_uuid',
          'auto_position',
          'io_running',
          'sql_running',
          'last_error',
          'last_io_error',
          'last_sql_error',
          'sql_running_state',
          'ts',
        ])
        ->condition('server_num', $srvnum, '=')
        ->execute()
        ->fetchAll();
      foreach ($results as $row) {
        $server_status = sprintf("<table class='db-charts-table'><thead><tr><th>Server Number : " .
          " %d</th><th>Master is : %s <span class='db-charts-masteronport'>on port</span> %s</th></tr></thead>" .
          "<tbody>" .
          "<tr><td><span class='label'>IO Running :</span> %s</td><td><span class='label'>SQL Running :</span> %s</td></tr>" .
          "<tr><td><span class='label'>Master_UUID :</span> %s</td><td><span class='label'>Auto_Position :</span> %s</td></tr>" .
          "<tr><td colspan='2'><span class='label'>Last Error :</span> %s</td></tr>" .
          "<tr><td colspan='2'><span class='label'>Last IO Error :</span> %s</td></tr>" .
          "<tr><td colspan='2'><span class='label'>Last SQL Error :</span> %s</td></tr>" .
          "<tr><td colspan='2'><span class='label'>SQL Running State :</span> %s</td></tr>" .
          "<tr><td colspan='2'><span class='label'>Last Updated :</span> %s</td></tr></tbody></table>",
          (int) $srvnum,
          Html::escape($row->master_host),
          Html::escape($row->master_port),
          Html::escape($row->io_running),
          Html::escape($row->sql_running),
          Html::escape($row->master_uuid),
          Html::escape($row->auto_position),
          Html::escape($row->last_error),
          Html::escape($row->last_io_error),
          Html::escape($row->last_sql_error),
          Html::escape($row->sql_running_state),
          Html::escape($row->ts)
        );
        $array_slave_status[$srvnum] = $server_status;
      }
      // End the autoslave_slave_status status_data array.
    }

    $array_of_masters = [];
    if (!autoslave_is_driver_loaded()) {
      // AUTOSLAVE DRIVER IS NOT LOADED.
      $array_for_json = NULL;
      $msg = t('autoslave driver is not loaded');
      $not_available = sprintf("<table class='db-charts-table'><thead><tr><th> %s" .
        "</th></tr></thead><tbody><tr><td>%s</td></tr></tbody></table>",
        Html::escape($msg), Html::escape($msg));
      $array_slave_status = [];
      $array_slave_status[1] = $not_available;
      $array_master_status = [];
      // $not_available;
      $array_master_status[1] = '';
      $array_of_servers = [];
      $array_of_servers[] = 1;
      $num_servers = 1;
      $asd_formatted_hashlink = NULL;
    }
    else {
      $array_master_status = $this->getMasterStatus();
      $num_of_masters = count($array_master_status);
      for ($m = 1; $m <= $num_of_masters; $m++) {
        $array_of_masters[] = $m;
      }
    }
    if ($this->moduleHandler->moduleExists('elysia_cron')) {
      if (elysia_cron_is_job_disabled('autoslave_cron')) {
        \Drupal::messenger()->addMessage("The elysia_cron module is enabled, the 'autoslave_cron' job is 'PAUSED'. " .
          "This is to prevent autoslave from overriding your manual invalidation because normally autoslave_cron " .
          "will test your server connections and automatically bring them back into load.  Normally this is what " .
          "you want however in a manual override you are probably doing maintenance on your server and don't" .
          " want it to be hit.  The 'autoslave_cron' job will be UN-PAUSED when you resume normal operation." .
          "If you are planning server maintenance on the invalidated server now would be a good time to run" .
          " 'stop slave' on that host.",
          'warning', TRUE);
      }
    }

    $dashboard = [
      'json_data' => json_encode($array_for_json, JSON_PRETTY_PRINT),
      'json_slave_status' => json_encode($array_slave_status, JSON_PRETTY_PRINT),
      'json_master_status' => json_encode($array_master_status, JSON_PRETTY_PRINT),
      'json_servers' => json_encode($array_of_servers, JSON_PRETTY_PRINT),
      'json_masters' => json_encode($array_of_masters, JSON_PRETTY_PRINT),
      'num_servers' => $num_servers,
      'num_masters' => count($array_master_status),
      'hashkey_link' => $asd_formatted_hashlink,
      'module_path' => \Drupal::service('extension.path.resolver')->getPath('module', 'autoslave'),
      // Variables go here, you can add more variables like json_data.
      // These variables are used in templates/autoslave_dashboard.tpl.php.
    ];

    $build = [
      '#theme' => 'autoslave_dashboard',
      '#dashboard' => $dashboard,
      '#attached' => [
        'library' => [
          'autoslave/autoslave',
        ],
      ],
    ];

    return $build;
  }

  /**
   * Generates hash from hostname.
   *
   * @return string
   *   Base-64 encoded, URL-safe sha-256 hash based on hostname.
   */
  public static function getHashFromHostname() {
    // Use trusted request host to prevent host header injection.
    $hostname = \Drupal::request()->getSchemeAndHttpHost();
    return Crypt::hashBase64(\Drupal::service('private_key')->get() . $hostname);
  }

  /**
   * Gets master database status information.
   *
   * @return array
   *   Array of master status information indexed by server number.
   */
  public function getMasterStatus() {

    $databases = Database::getAllConnectionInfo();
    $output = '';
    $master_num_serv = 0;
    $some_invalidation_is_kicked_in = FALSE;
    foreach ($databases as $key => $targets) {
      foreach ($targets as $target => $conninfo) {
        $conninfos = empty($conninfo['driver']) ? $conninfo : [$conninfo];
        foreach ($conninfos as $conninfo) {
          if ($conninfo['driver'] != 'autoslave') {
            continue;
          }
          $output .= "<h2>[$key][$target]</h2>";
          try {
            $conn = Database::getConnection($target, $key);
            $pool = $conn->getPool();
            $invalidation_file = '';
            $invalidation_message = t('Autoslave is in default operation mode');
            if (isset($conn->connectionOptions['invalidation path'])) {
              $invalidation_file = $conn->connectionOptions['invalidation path'] . "/autoslave-invalidation-$key.inc";
              if (file_exists($invalidation_file)) {
                $some_invalidation_is_kicked_in = TRUE;
                $invalidation_message = t('Invalidation mode enabled with @filepath', ['@filepath' => $invalidation_file]);
              }
            }
            else {
              $invalidation_file = "public://autoslave-invalidation-$key.inc";
              if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($invalidation_file)) {
                $invalidation_file_real = $wrapper->realpath();
              }
              if (file_exists($invalidation_file_real)) {
                $some_invalidation_is_kicked_in = TRUE;
                $invalidation_message = t('Invalidation mode enabled with @filepath', ['@filepath' => $invalidation_file_real]);
              }
            }
            foreach ($pool['master'] as $target_master => $conninfos) {
              if ($conn->determineMasterTarget() == $target_master) {
                $target_master_info = '<strong>' . Html::escape($target_master) . '</strong>';
                $master_num_serv++;
                // watchdog('autoslave_DEBUG_MASTER','<pre>@conninfos</pre>', array('@conninfos'=> print_r($conninfos, TRUE)));.
                $master_host = $conninfos[$master_num_serv]['host'] ?? t('Error reading hostname');
                $master_port = $conninfos[$master_num_serv]['port'] ?? 3306;
                $server_status = sprintf("<table data-autoslave-invalidation='%d' data-target-master='%s' data-target-host='%s' data-target-port='%s' class='db-charts-table is-master'><thead><tr><th>%s at host %s port %s" .
                  "<span class='db-charts-masteronport'></span></th></tr></thead>" .
                  "<tbody><tr><td><span class='label'><strong>Currently live as a master (autoslave valid).</strong></span></td></tr><tr><td><span class='label'>" .
                  "Key: %s Target: %s <br>Pool: Master</span></td></tr><tr><td><span class='label'>%s</span></td></tr>",
                  (int) $some_invalidation_is_kicked_in,
                  Html::escape($target_master),
                  Html::escape($master_host),
                  (int) $master_port,
                  Html::escape($target_master),
                  Html::escape($master_host),
                  (int) $master_port,
                  Html::escape($key),
                  Html::escape($target),
                  Html::escape($invalidation_message)
                );
              }
              elseif ($conn->determineMasterTarget() != $target_master) {
                $target_master_info = '<strong>' . Html::escape($target_master) . '</strong>';
                $master_num_serv++;
                $autoslave_status_for_server = 0;
                $server_status_message = t('currently disabled (autoslave invalid).');
                if (isset($conninfos[$master_num_serv]['status'])) {
                  $autoslave_status_for_server = $conninfos[$master_num_serv]['status'];
                }
                if ($autoslave_status_for_server || !$some_invalidation_is_kicked_in) {
                  $server_status_message = t('currently enabled (autoslave valid) but not live as a master.');
                }
                else {
                  $autoslave_status_for_server = 0;
                }
                // watchdog('autoslave_DEBUG_TEMPORARY','<pre>@conninfos</pre>', array('@conninfos'=> print_r($conninfos, TRUE)));.
                $master_host = $conninfos[$master_num_serv]['host'] ?? t('Error reading hostname');
                $master_port = $conninfos[$master_num_serv]['port'] ?? 3306;
                $server_status = sprintf("<table data-autoslave-invalidation='%d' data-target-master='%s' data-target-host='%s' data-target-port='%s' class='db-charts-table is-slave'><thead><tr><th>%s at host %s port %s" .
                  "<span class='db-charts-masteronport'></span></th></tr></thead>" .
                  "<tbody><tr><td><span class='label'><strong>Server is %s</strong></span></td></tr><tr><td><span class='label'>" .
                  "Key: %s Target: %s <br>Pool: Master</span></td></tr><tr><td><span class='label'>%s</span></td></tr>",
                  (int) $some_invalidation_is_kicked_in,
                  Html::escape($target_master),
                  Html::escape($master_host),
                  (int) $master_port,
                  Html::escape($target_master),
                  Html::escape($master_host),
                  (int) $master_port,
                  Html::escape($server_status_message),
                  Html::escape($key),
                  Html::escape($target),
                  Html::escape($invalidation_message)
                );
              }
              $array_master_status[$master_num_serv] = $server_status;
            }
          }
          catch (\Exception $e) {
            \Drupal::messenger()->addMessage('Error connecting to: [@key][@target]', [
              '@key' => $key,
              '@target' => $target,
            ], 'error');
          }
        }
      }
    }

    return $array_master_status;
  }

  /**
   * Collects statistics from MySQL for autoslave dashboard.
   *
   * This function is called by autoslave_cron to gather server statistics.
   */
  public static function collectDashboardStatistics() {
    $databases = Database::getAllConnectionInfo();
    $config = \Drupal::config('autoslave.settings');
    $debug_mode = $config->get('autoslave_debug_mode', FALSE);
    $n_select = 0;
    $n_insert = 0;
    $n_update = 0;
    $srv_num = 0;
    $host_port = [];
    foreach ($databases as $key => $targets) {
      foreach ($targets as $target => $conninfo) {
        $conninfos = empty($conninfo['driver']) ? $conninfo : [$conninfo];
        foreach ($conninfos as $conninfo) {
          if ($conninfo['driver'] != 'mysql') {
            // Skip non-mysql drivers.
            continue;
          }
          $n_select = 0;
          $n_insert = 0;
          $n_update = 0;
          $conninfo_host = $conninfo['host'] ?? '';
          $conninfo_port = $conninfo['port'] ?? '3306';
          if (empty($conninfo_port)) {
            $conninfo_port = 3306;
          }
          if (empty($conninfo_host) || !isset($conninfo['username'])) {
            continue;
          }
          $hostport_string = $conninfo_port . $conninfo_host;
          if (in_array($hostport_string, $host_port)) {
            // This server host and port was previously processed, skipping.
            continue;
          }
          if ($debug_mode) {
            \Drupal::logger('autoslave_cron_debug')->notice("[@key][@target] port = @port host = @host server_num = @srv_num", [
              '@key' => $key,
              '@target' => $target,
              '@port' => $conninfo_port,
              '@host' => $conninfo_host,
              '@srv_num' => $srv_num,
            ]);
          }
          $host_port[] = $hostport_string;
          // Tests to see if this target is a 100% slave or not.
          $test_for_slave = stripos($target, 'slave');
          $srv_num = ($conninfo['autoslave_id'] ?? $srv_num = $srv_num + 1);
          // Keep this in scope for this loop.
          $sdb = '';
          // BEGIN PROCESSING FOR NEW autoslave_server_stats.
          try {
            /*
             * Intentionally changed this query to mysqli from Database::getConnection($target, $key)->query.
             * Made this change to ensure going to the intended database because
             * Database::getConnection for target slave occasionally would point to the master
             * grabbing incorrect statistics and skewing results.  mysqli connection ensures bypassing autoslave.
             **/
            $sdb = new \mysqli($conninfo_host, $conninfo['username'], $conninfo['password'], '', $conninfo_port);
            $result = $sdb->query("show global status where Variable_name='Com_select'");
            if (empty($result)) {
              $n_select = 0;
              $n_update = 0;
              $n_insert = 0;
              \Drupal::messenger()->addMessage("Insufficient priviledges to run the mysql command \"show global status where Variable_name='Com_select';\"" .
                ' on server_num @srv_num [@key][@target] at host: @host on port @port.  You must grant the ' .
                'correct permissions to @user@\'@host\';"', [
                  '@host' => $conninfo['host'] ?? 'host',
                  '@user' => $conninfo['username'] ?? 'username',
                  '@key' => $key,
                  '@port' => !empty($conninfo['port']) ? $conninfo['port'] : '3306',
                  '@target' => $target,
                  '@srv_num' => $srv_num,
                ], 'warning', TRUE);
            }
            else {
              $row = $result->fetch_array();
              $n_select = $row['Value'];
              $result = $sdb->query("show global status where Variable_name='Com_insert'");
              $row = $result->fetch_array();
              $n_insert = $row['Value'];
              $result = $sdb->query("show global status where Variable_name='Com_update'");
              $row = $result->fetch_array();
              $n_update = $row['Value'];
            }
          }
          catch (\Exception $e) {
            // Do some logging, error handling...
            $n_select = 0;
            \Drupal::logger('autoslave_cron')->error("Error connecting to: [@key][@target] port = @port host = @host", [
              '@key' => $key,
              '@target' => $target,
              '@port' => $conninfo_port,
              '@host' => $conninfo_host,
            ]);
          }
          try {
            // Table name no longer needs {}.
            $insert_return_code = \Drupal::database()->insert('autoslave_server_stats')
              ->fields([
                'server_num' => $srv_num,
                'n_select' => $n_select,
                'n_insert' => $n_insert,
                'n_update' => $n_update,
                'ts' => date('Y-m-d H:i:s'),
              ])
              ->execute();
          }
          catch (\Exception $e) {
            \Drupal::logger('autoslave_cron')->error('Error inserting into table autoslave_server_stats on: [@key][@target]', [
              '@key' => $key,
              '@target' => $target,
            ]);
          }
          // END PROCESSING FOR NEW autoslave_server_stats.
          // BEGIN TRIMMING OLDER autoslave_server_stats.
          try {
            // Keep the database trimmed.
            $number_of_days_to_keep = 4;
            $del_before = date('Y-m-d H:i:s', time() - $number_of_days_to_keep * 86400);
            // Drupal 7.
            $num_deleted = \Drupal::database()->delete('autoslave_server_stats')
              ->condition('server_num', $srv_num)
              ->condition('ts', $del_before, '<')
              ->execute();
          }
          catch (\Exception $e) {
            \Drupal::logger('autoslave_cron')->error('Error deleting rows from the autoslave_server_stats table on: [@key][@target]', [
              '@key' => $key,
              '@target' => $target,
            ]);
          }
          // END TRIMMING OLDER autoslave_server_stats.
          // BEGIN autoslave_slave_status processing , note this will only get data from slaves.
          try {
            \Drupal::database()->delete('autoslave_slave_status')
              ->condition('server_num', $srv_num, '=')
              ->execute();
          }
          catch (\Exception $e) {
            // Do nothing, in this case it's ok, we needed to delete the old $srv_num row, and if there wasn't one already it is ok.
            \Drupal::logger('autoslave_cron')->notice('Exception message: @e', ['@e' => $e]);
          }
          try {
            // Update the autoslave_slave_status table.
            $fields_array = [];
            if ($test_for_slave !== FALSE) {
              // Sleep 2 seconds. (mostly because of my lame dev setup (3 mysql servers on same slow vm host different ports).
              sleep(2);
            }
            $bShowSlaveSuccess = FALSE;
            /*
             * Intentionally changed this query to mysqli from Database::getConnection($target, $key)->query.
             * Made this change to ensure going to the intended database because
             * Database::getConnection for target slave occasionally would point to the master
             * grabbing information from the incorrect server.  mysqli connection ensures bypassing autoslave.
             **/
            $sdb = new \mysqli($conninfo_host, $conninfo['username'], $conninfo['password'], '', $conninfo_port);
            $result = $sdb->query("show slave status");
            if (empty($result)) {
              throw new \Exception('Need permissions for replication client.');
            }
            $row = $result->fetch_array();
            if (!empty($row)) {
              $bShowSlaveSuccess = TRUE;
            }
            $fields_array = [
              'server_num' => $srv_num,
              'io_state' => substr($row['Slave_IO_State'], 0, 256),
              'master_host' => substr($row['Master_Host'], 0, 256),
              'master_port' => $row['Master_Port'],
              'master_uuid' => substr($row['Master_UUID'], 0, 256),
              'auto_position' => $row['Auto_Position'],
              'io_running' => $row['Slave_IO_Running'],
              'sql_running' => $row['Slave_SQL_Running'],
              'last_error' => $row['Last_Error'],
              'last_io_error' => $row['Last_IO_Error'],
              'last_sql_error' => $row['Last_SQL_Error'],
              'sql_running_state' => substr("host $conninfo_host port $conninfo_port " . $row['Slave_SQL_Running_State'], 0, 256),
              'ts' => date('Y-m-d H:i:s'),
            ];
            if (!$bShowSlaveSuccess) {
              $text_master_or_slave = 'Master is up and running.';
              if ($test_for_slave !== FALSE) {
                $text_master_or_slave = 'Slave is up and running and currently processing the relay logs.';
              }
              $fields_array = [
                'server_num' => $srv_num,
                'io_state' => '',
                'master_host' => $conninfo['host'] ?? 'host',
                'master_port' => '',
                'master_uuid' => '',
                'io_running' => '',
                'sql_running' => '',
                'last_error' => '',
                'last_io_error' => '',
                'last_sql_error' => '',
                'sql_running_state' => "host $conninfo_host port $conninfo_port " . $text_master_or_slave,
                'ts' => date('Y-m-d H:i:s'),
              ];
              if ($test_for_slave !== FALSE) {
                \Drupal::logger('autoslave_cron_helper')->notice('No records found for show slave status on [@key][@target] at server_num @srv_num ' .
                  '@host on port @port appears to be a slave server.  Have you granted REPLICATION CLIENT to the mysql user @user ' .
                  ' to run "show slave status" on: [@key][@target]?', [
                    '@host' => $conninfo['host'] ?? 'host',
                    '@user' => $conninfo['username'] ?? 'username',
                    '@key' => $key,
                    '@port' => !empty($conninfo['port']) ? $conninfo['port'] : '3306',
                    '@target' => $target,
                    '@srv_num' => $srv_num,
                  ]);
              }
            }
            $insert_return_code = \Drupal::database()->insert('autoslave_slave_status')->fields($fields_array)->execute();
          }
          catch (\Exception $e) {
            if ($test_for_slave) {
              /*watchdog('autoslave_cron_helper', '(Slave server) Insufficient priviledges to run the mysql command "show slave status;"' .
              ' on server_num @srv_num [@key][@target] at host: @host on port @port.  You must grant the ' .
              'correct permissions as follows: "grant REPLICATION CLIENT on *.* to @user@\'@host\';" ' .
              'server_num ' . $srv_num . ' at host: @host on port @port appears to be a slave server. ' .
              ' The mysql user @user is probably lacking permissions to run "show slave status" on: ' .
              '[@key][@target].  Full exception message: @e', array(
              '@host' => isset($conninfo['host']) ? $conninfo['host'] : 'host',
              '@user' => isset($conninfo['username']) ? $conninfo['username'] : 'username',
              '@key' => $key,
              '@port' => !empty($conninfo['port']) ? $conninfo['port'] : '3306',
              '@target' => $target,
              '@srv_num' =>  $srv_num,
              '@e' => $e,
              ), WATCHDOG_NOTICE);*/
            }
            else {
              /* watchdog('autoslave_cron_helper', '(Master or secondary master. server) You must grant the correct permissions on server_num @srv_num as follows: ' .
              '"grant REPLICATION CLIENT on *.* to @user@\'@host\';"  the @user is probably lacking permissions for ' .
              '"show slave status" on: [@key][@target].  Full exception message: @e', array(
              '@host' => isset($conninfo['host']) ? $conninfo['host'] : 'host',
              '@user' => isset($conninfo['username']) ? $conninfo['username'] : 'username',
              '@key' => $key,
              '@port' => !empty($conninfo['port']) ? $conninfo['port'] : '3306',
              '@target' => $target,
              '@srv_num' =>  $srv_num,
              '@e' => $e,
              ), WATCHDOG_NOTICE);*/
            }
            \Drupal::messenger()->addMessage('Insufficient priviledges to run the mysql command "show slave status;"' .
              ' on server_num @srv_num [@key][@target] at host: @host on port @port.  You must grant the ' .
              'correct permissions as follows: "grant REPLICATION CLIENT on *.* to @user@\'@host\';"', [
                '@host' => $conninfo['host'] ?? 'host',
                '@user' => $conninfo['username'] ?? 'username',
                '@key' => $key,
                '@port' => !empty($conninfo['port']) ? $conninfo['port'] : '3306',
                '@target' => $target,
                '@srv_num' => $srv_num,
              ], 'warning', TRUE);
          }
          // END autoslave_slave_status processing , note this will only get data from slaves.
          // grant REPLICATION CLIENT on *.* to user@'host';*/.
        }
      }
    }
  }

  /**
   * AJAX callback for manual server invalidation override.
   *
   * Path: admin/autoslave/serverhost/serverport/invalidate.
   *
   * @param string $hostname
   *   The server hostname to invalidate.
   * @param int $port
   *   The server port to invalidate.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with status and data.
   */
  public function autoslaveInvalidate($hostname, $port) {
    // Validate and sanitize hostname parameter to prevent injection attacks.
    if (!is_string($hostname) || empty($hostname)) {
      return new JsonResponse([
        'status' => FALSE,
        'error' => 'Invalid hostname parameter',
      ], 400);
    }

    // Validate port parameter - must be numeric and in valid range.
    if (!is_numeric($port) || $port < 1 || $port > 65535) {
      return new JsonResponse([
        'status' => FALSE,
        'error' => 'Invalid port parameter',
      ], 400);
    }

    // Use htmlspecialchars instead of deprecated FILTER_SANITIZE_STRING (deprecated in PHP 8.1).
    $server_host = htmlspecialchars($hostname, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $server_port = (int) $port;
    $invalidation_is_kicked_in = FALSE;
    // Make t() function calls prior to invalidation, this avoids a 500 error.
    // A 500 error avoid because this thread db connection will be invalidated
    // so avoid db access, do it now, avoid immediately after invalidation.
    $invalidation_failed = t('Autoslave is in default operation mode, invalidation did not kick in.');
    $invalidation_message = t('Autoslave is in invalidation mode using this file:');
    $stop_slave_msg = t("for host: @host on port @port. If you're planning server maintenance on host @host port @port down then you now should run 'stop slave'", [
      '@host' => $server_host,
      '@port' => $server_port,
    ]);
    $elysia_msg = '';

    if (empty($server_host)) {
      $message = t('@failed for host: @host on port @port.', [
        '@failed' => $invalidation_failed,
        '@host' => $server_host,
        '@port' => $server_port,
      ]);
      \Drupal::messenger()->addMessage($message, 'warning', TRUE);
      return new JsonResponse(['status' => FALSE, 'data' => $invalidation_failed], JSON_PRETTY_PRINT);
    }
    if (empty($server_port)) {
      // Default the server port to 3306 when no server port is provided.
      $server_port = 3306;
    }
    if ($this->moduleHandler->moduleExists('elysia_cron')) {
      elysia_cron_set_job_disabled('autoslave_cron', TRUE);
      $elysia_msg = $this->t('ALSO TEMPORARILY DISABLED ELYSIA CRON FOR autoslave');
    }
    $databases = Database::getAllConnectionInfo();
    foreach ($databases as $key => $targets) {
      foreach ($targets as $target => $conninfo) {
        $conninfos = empty($conninfo['driver']) ? $conninfo : [$conninfo];
        foreach ($conninfos as $conninfo) {
          if ($conninfo['driver'] != 'autoslave') {
            continue;
          }
          try {
            $conn = Database::getConnection($target, $key);
            $pool = $conn->getPool();

            $invalidation_file = '';
            foreach ($pool['master'] as $target_master => $masterconninfos) {
              // If ($conn->determineMasterTarget() == $target_master) {
              // $conn for to the current live master.
              // }.
              foreach ($masterconninfos as $id => $connection) {
                if (isset($connection['port']) && isset($connection['host'])) {
                  if ($server_port == $connection['port'] && $server_host == $connection['host']) {
                    $conn->invalidateConnection($id);
                  }
                }
              }
              foreach ($pool['slave'] as $target_slave => $slaveconninfos) {
                foreach ($slaveconninfos as $id => $connection) {
                  if (isset($connection['port']) && isset($connection['host'])) {
                    if ($server_port == $connection['port'] && $server_host == $connection['host']) {
                      $conn->invalidateConnection($id);
                    }
                  }
                }
              }
            }
            if (isset($conn->connectionOptions['invalidation path'])) {
              $invalidation_file = $conn->connectionOptions['invalidation path'] . "/autoslave-invalidation-$key.inc";
              if (file_exists($invalidation_file)) {
                $invalidation_is_kicked_in = TRUE;
                $invalidation_message .= $invalidation_file;
              }
            }
            else {
              $invalidation_file = "public://autoslave-invalidation-$key.inc";
              if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($invalidation_file)) {
                $invalidation_file_real = $wrapper->realpath();
              }
              if (file_exists($invalidation_file_real)) {
                $invalidation_is_kicked_in = TRUE;
                $invalidation_message .= $invalidation_file;
              }
            }
          }
          catch (\Exception $e) {
            \Drupal::logger('autoslave_invalidate_server')->error('<pre>@e</pre>', ['@e' => print_r($e, TRUE)]);
          }
        }
      }
    }
    if ($invalidation_is_kicked_in) {
      return new JsonResponse([
        'status' => TRUE,
        'data' => $invalidation_message . $elysia_msg .
        $stop_slave_msg,
      ]);
    }
    else {
      if ($this->moduleHandler->moduleExists('elysia_cron')) {
        elysia_cron_set_job_disabled('autoslave_cron', FALSE);
      }
      $message = t('@failed for host: @host on port @port.', [
        '@failed' => $invalidation_failed,
        '@host' => $server_host,
        '@port' => $server_port,
      ]);
      \Drupal::messenger()->addMessage($message, 'error', TRUE);
      return new JsonResponse(['status' => FALSE, 'data' => $invalidation_failed]);
    }
  }

}
