<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\State\StateInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Psr\Log\LoggerInterface;

/**
 * Service for checking and repairing field definition integrity.
 *
 * @internal
 */
final class FieldIntegrityService implements FieldIntegrityServiceInterface {

  /**
   * State key for the last integrity check timestamp.
   */
  private const LAST_CHECK_KEY = 'ffmpeg_media.last_integrity_check';

  /**
   * Constructs a FieldIntegrityService object.
   *
   * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager
   *   The entity definition update manager.
   * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $lastInstalledSchemaRepository
   *   The last installed schema repository.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   The entity field manager.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|null $keyValueFactory
   *   The key-value factory (optional for backwards compatibility).
   */
  public function __construct(
    protected readonly EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager,
    protected readonly EntityLastInstalledSchemaRepositoryInterface $lastInstalledSchemaRepository,
    protected readonly EntityFieldManagerInterface $entityFieldManager,
    protected readonly StateInterface $state,
    protected readonly LoggerInterface $logger,
    protected readonly ?KeyValueFactoryInterface $keyValueFactory = NULL,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function check(): array {
    $problems = [];
    $trackedFields = $this->getTrackedFields();

    foreach ($trackedFields as $entry) {
      $entityType = $entry['entity_type'];
      $fieldName = $entry['field_name'];

      // Check if FieldStorageConfig entity exists.
      $fieldStorage = FieldStorageConfig::loadByName($entityType, $fieldName);

      // Check if installed definition exists in key-value store.
      $installedDefinitions = $this->lastInstalledSchemaRepository
        ->getLastInstalledFieldStorageDefinitions($entityType);
      $hasInstalledDefinition = isset($installedDefinitions[$fieldName]);

      if ($hasInstalledDefinition && $fieldStorage === NULL) {
        $problems[] = [
          'type' => 'orphaned_definition',
          'entity_type' => $entityType,
          'field_name' => $fieldName,
          'details' => "Installed definition exists for {$entityType}.{$fieldName} but no FieldStorageConfig entity found.",
        ];
      }
      elseif (!$hasInstalledDefinition && $fieldStorage !== NULL) {
        $problems[] = [
          'type' => 'missing_definition',
          'entity_type' => $entityType,
          'field_name' => $fieldName,
          'details' => "FieldStorageConfig exists for {$entityType}.{$fieldName} but no installed definition found.",
        ];
      }
    }

    // Check for pending deletions via the update manager.
    $changeList = $this->entityDefinitionUpdateManager->getChangeSummary();
    foreach ($changeList as $entityTypeId => $changes) {
      foreach ($changes as $change) {
        $changeStr = (string) $change;
        if (str_contains($changeStr, 'deleted') || str_contains($changeStr, 'gelöscht')) {
          foreach ($trackedFields as $entry) {
            if ($entry['entity_type'] === $entityTypeId
              && str_contains($changeStr, $entry['field_name'])) {
              $problems[] = [
                'type' => 'pending_deletion',
                'entity_type' => $entityTypeId,
                'field_name' => $entry['field_name'],
                'details' => "Pending deletion detected: {$changeStr}",
              ];
            }
          }
        }
      }
    }

    $this->state->set(self::LAST_CHECK_KEY, time());

    return $problems;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function repair(array $problems = []): array {
    if (empty($problems)) {
      $problems = $this->check();
    }

    $result = [
      'repaired' => 0,
      'failed' => 0,
      'details' => [],
    ];

    if (empty($problems)) {
      $result['details'][] = 'No problems found.';
      return $result;
    }

    foreach ($problems as $problem) {
      $success = match ($problem['type']) {
        'orphaned_definition' => $this->repairOrphanedDefinition(
          $problem['entity_type'],
          $problem['field_name'],
        ),
        'missing_definition' => $this->repairMissingDefinition(
          $problem['entity_type'],
          $problem['field_name'],
        ),
        'pending_deletion' => $this->repairPendingDeletion(
          $problem['entity_type'],
          $problem['field_name'],
        ),
        default => FALSE,
      };

      if ($success) {
        $result['repaired']++;
        $result['details'][] = "Repaired: {$problem['details']}";
      }
      else {
        $result['failed']++;
        $result['details'][] = "Failed to repair: {$problem['details']}";
      }
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function getLastCheckTime(): ?int {
    $time = $this->state->get(self::LAST_CHECK_KEY);
    return $time !== NULL ? (int) $time : NULL;
  }

  /**
   * Repairs an orphaned installed definition (no backing FieldStorageConfig).
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $fieldName
   *   The field name.
   *
   * @return bool
   *   TRUE if repair succeeded.
   */
  protected function repairOrphanedDefinition(string $entityType, string $fieldName): bool {
    try {
      $installedDefinitions = $this->lastInstalledSchemaRepository
        ->getLastInstalledFieldStorageDefinitions($entityType);

      if (!isset($installedDefinitions[$fieldName])) {
        return TRUE;
      }

      $definition = $installedDefinitions[$fieldName];

      // Try using the update manager first.
      try {
        $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($definition);
        $this->logger->info('Removed orphaned field definition @entity_type.@field via update manager.', [
          '@entity_type' => $entityType,
          '@field' => $fieldName,
        ]);
        return TRUE;
      }
      catch (\Throwable $e) {
        $this->logger->warning('Update manager failed for @entity_type.@field, trying direct cleanup: @message', [
          '@entity_type' => $entityType,
          '@field' => $fieldName,
          '@message' => $e->getMessage(),
        ]);
      }

      // Fallback: direct key-value store manipulation.
      if ($this->keyValueFactory === NULL) {
        return FALSE;
      }
      $keyValueStore = $this->keyValueFactory->get('entity.definitions.installed');
      $key = "{$entityType}.field_storage_definitions";
      $definitions = $keyValueStore->get($key, []);

      if (isset($definitions[$fieldName])) {
        unset($definitions[$fieldName]);
        $keyValueStore->set($key, $definitions);
        $this->logger->info('Removed orphaned field definition @entity_type.@field via direct key-value cleanup.', [
          '@entity_type' => $entityType,
          '@field' => $fieldName,
        ]);
        return TRUE;
      }

      return TRUE;
    }
    catch (\Throwable $e) {
      $this->logger->error('Failed to repair orphaned definition @entity_type.@field: @message', [
        '@entity_type' => $entityType,
        '@field' => $fieldName,
        '@message' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Repairs a missing installed definition (FieldStorageConfig exists).
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $fieldName
   *   The field name.
   *
   * @return bool
   *   TRUE if repair succeeded.
   */
  protected function repairMissingDefinition(string $entityType, string $fieldName): bool {
    try {
      $fieldStorage = FieldStorageConfig::loadByName($entityType, $fieldName);
      if ($fieldStorage === NULL) {
        return FALSE;
      }

      // Re-saving the FieldStorageConfig triggers the installed definition
      // to be written by the entity storage.
      $fieldStorage->save();
      $this->logger->info('Re-saved FieldStorageConfig for @entity_type.@field to restore installed definition.', [
        '@entity_type' => $entityType,
        '@field' => $fieldName,
      ]);
      return TRUE;
    }
    catch (\Throwable $e) {
      $this->logger->error('Failed to repair missing definition @entity_type.@field: @message', [
        '@entity_type' => $entityType,
        '@field' => $fieldName,
        '@message' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Repairs a pending deletion.
   *
   * @param string $entityType
   *   The entity type ID.
   * @param string $fieldName
   *   The field name.
   *
   * @return bool
   *   TRUE if repair succeeded.
   */
  protected function repairPendingDeletion(string $entityType, string $fieldName): bool {
    try {
      $installedDefinitions = $this->lastInstalledSchemaRepository
        ->getLastInstalledFieldStorageDefinitions($entityType);

      if (!isset($installedDefinitions[$fieldName])) {
        return TRUE;
      }

      $fieldStorage = FieldStorageConfig::loadByName($entityType, $fieldName);

      if ($fieldStorage === NULL) {
        // No backing config, treat as orphaned.
        return $this->repairOrphanedDefinition($entityType, $fieldName);
      }

      // FieldStorageConfig exists, re-save to sync definitions.
      $fieldStorage->save();
      $this->logger->info('Re-saved FieldStorageConfig for @entity_type.@field to resolve pending deletion.', [
        '@entity_type' => $entityType,
        '@field' => $fieldName,
      ]);
      return TRUE;
    }
    catch (\Throwable $e) {
      $this->logger->error('Failed to repair pending deletion @entity_type.@field: @message', [
        '@entity_type' => $entityType,
        '@field' => $fieldName,
        '@message' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Gets the list of fields tracked by this module.
   *
   * @return array<int, array{entity_type: string, field_name: string}>
   *   List of tracked fields.
   */
  protected function getTrackedFields(): array {
    $tracked = $this->state->get('ffmpeg_media.created_fields', []);

    if (!is_array($tracked)) {
      return [];
    }

    return $tracked;
  }

}
