<?php

declare(strict_types=1);

namespace Drupal\acquia_dam\Service;

use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\media\MediaTypeInterface;

/**
 * Service for handling Y2038 compatibility for timestamp fields.
 *
 * This service ensures that timestamp fields mapped to DAM date metadata
 * use BIGINT instead of INT to support dates beyond January 19, 2038.
 */
class Y2038CompatibilityService {

  use StringTranslationTrait;


  /**
   * Constructs a Y2038CompatibilityService object.
   *
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   The entity field manager service.
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   The logger factory.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   */
  public function __construct(
    private readonly EntityFieldManagerInterface $entityFieldManager,
    private readonly Connection $database,
    private readonly LoggerChannelFactoryInterface $loggerFactory,
    private readonly MessengerInterface $messenger,
  ) {}

  /**
   * Ensures timestamp fields mapped to DAM date fields use BIGINT.
   *
   * DAM can have date values beyond 2038, which cannot be stored in a
   * standard 32-bit INT field. This method converts the specified timestamp
   * fields to use BIGINT.
   *
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type being saved.
   * @param array $field_names
   *   Array of field names to convert to BIGINT. If empty, processes all
   *   mapped timestamp fields.
   * @param bool $show_message
   *   Whether to show user messages. Default TRUE.
   *
   * @return int
   *   Number of fields updated.
   */
  public function ensureBigintForMappedDateFields(MediaTypeInterface $media_type, array $field_names = [], bool $show_message = TRUE): int {
    $schema = $this->database->schema();
    $updated_fields = [];

    // Load field definitions once for this media type.
    $field_definitions = $this->entityFieldManager->getFieldDefinitions('media', $media_type->id());

    foreach ($field_names as $drupal_field) {
      // Get specific field definition for this field.
      if (!isset($field_definitions[$drupal_field])) {
        continue;
      }

      $field_definition = $field_definitions[$drupal_field];
      // Only process timestamp fields. Skip base fields.
      if ($field_definition->getType() !== 'timestamp' || $field_definition->getFieldStorageDefinition()->isBaseField()) {
        continue;
      }

      $field_name = $field_definition->getName();
      $table_name = 'media__' . $field_name;
      $column_name = $field_name . '_value';

      // Check if table and column exist.
      if (!$schema->tableExists($table_name) || !$schema->fieldExists($table_name, $column_name)) {
        continue;
      }

      try {
        // Define the field spec for BIGINT conversion.
        $field_spec = [
          'type' => 'int',
          'size' => 'big',
          'not null' => FALSE,
        ];

        // Convert main table.
        $schema->changeField($table_name, $column_name, $column_name, $field_spec);

        // Convert revision table if it exists.
        $revision_table = 'media_revision__' . $field_name;
        if ($schema->tableExists($revision_table) && $schema->fieldExists($revision_table, $column_name)) {
          $schema->changeField($revision_table, $column_name, $column_name, $field_spec);
        }

        $updated_fields[] = [
          'field_label' => (string) $field_definition->getLabel(),
          'field_name' => $field_name,
        ];

        // Log the change.
        $this->loggerFactory->get('acquia_dam')->info('Updated field @field_name to BIGINT for Y2038 compatibility on media type @media_type.', [
          '@field_name' => $field_name,
          '@media_type' => $media_type->id(),
        ]);
      }
      catch (\Exception $e) {
        $this->loggerFactory->get('acquia_dam')->error('Failed to update field @field_name to BIGINT: @error', [
          '@field_name' => $field_name,
          '@error' => $e->getMessage(),
        ]);
      }
    }

    // Show user message if any fields were updated.
    if ($show_message && !empty($updated_fields)) {
      $this->showUpdateMessage($updated_fields);
    }

    return count($updated_fields);
  }

  /**
   * Finds timestamp fields that need BIGINT conversion.
   *
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type to check.
   * @param bool $only_changed
   *   If TRUE, only returns fields with changed mappings.
   *   If FALSE, returns all mapped timestamp fields.
   *   Default FALSE.
   * @param array $original_map
   *   The original field map before changes.
   *
   * @return array
   *   Array of field names that need BIGINT conversion.
   */
  public function findFieldsNeedingBigintConversion(MediaTypeInterface $media_type, bool $only_changed = FALSE, array $original_map = []): array {
    $fields_need_update = [];
    $new_map = $media_type->getFieldMap();

    // Load field definitions once for this media type.
    $field_definitions = $this->entityFieldManager->getFieldDefinitions('media', $media_type->id());
    foreach ($new_map as $dam_field => $drupal_field) {
      // Skip if not mapped or mapped to '_none'.
      if (empty($drupal_field) || $drupal_field === '_none') {
        continue;
      }

      // If only checking changed fields, skip unchanged mappings.
      if ($only_changed && !empty($original_map)) {
        if (isset($original_map[$dam_field]) && $original_map[$dam_field] === $drupal_field) {
          continue;
        }
      }

      // Get field definition.
      if (!isset($field_definitions[$drupal_field])) {
        continue;
      }

      $field_definition = $field_definitions[$drupal_field];

      // Only collect timestamp fields (skip base fields - they're handled separately).
      if ($field_definition->getType() === 'timestamp' && !$field_definition->getFieldStorageDefinition()->isBaseField()) {
        $fields_need_update[] = $drupal_field;
      }
    }

    return array_unique($fields_need_update);
  }

  /**
   * Updates the base expiry date field to use BIGINT.
   *
   * This method is called during module installation to ensure the
   * acquia_dam_expiry_date base field uses BIGINT.
   */
  public function updateExpiryDateFieldToBigint(): void {
    $schema = $this->database->schema();

    $tables = [
      'media__acquia_dam_expiry_date',
      'media_revision__acquia_dam_expiry_date',
    ];
    $field = 'acquia_dam_expiry_date_value';
    $field_spec = [
      'type' => 'int',
      'size' => 'big',
      'not null' => FALSE,
    ];

    foreach ($tables as $table) {
      if (!$schema->tableExists($table) || !$schema->fieldExists($table, $field)) {
        continue;
      }
      $schema->changeField($table, $field, $field, $field_spec);
    }
  }


  /**
   * Displays a message about fields updated to BIGINT.
   *
   * @param array $updated_fields
   *   Array of updated field information.
   */
  public function showUpdateMessage(array $updated_fields): void {
    $field_messages = array_map(function ($field_info) {
      return $this->t('"@field_label" (@field_name)', [
        '@field_label' => $field_info['field_label'],
        '@field_name' => $field_info['field_name'],
      ]);
    }, $updated_fields);

    $this->messenger->addStatus($this->t('The following timestamp field(s) were automatically upgraded to BIGINT to support dates beyond 2038: @fields', [
      '@fields' => implode(', ', $field_messages),
    ]));
  }

}
