<?php

declare(strict_types=1);

namespace Drupal\KernelTests\Core\Entity;

use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldStorageConfig;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

/**
 * Tests Field translation SQL Storage.
 */
#[Group('Entity')]
#[RunTestsInSeparateProcesses]
class FieldTranslationSqlStorageTest extends EntityLanguageTestBase {

  /**
   * Tests field SQL storage.
   */
  public function testFieldSqlStorage(): void {
    $entity_type = 'entity_test_mul';

    $controller = $this->entityTypeManager->getStorage($entity_type);
    $values = [
      $this->fieldName => $this->randomMachineName(),
      $this->untranslatableFieldName => $this->randomMachineName(),
    ];
    /** @var \Drupal\entity_test\Entity\EntityTestMul $entity */
    $entity = $controller->create($values);
    $entity->save();

    // Tests that when changing language field language codes are still correct.
    $langcode = $this->langcodes[0];
    $entity->langcode->value = $langcode;
    $entity->save();
    $this->assertFieldStorageLangcode($entity, 'Field language successfully changed from language neutral.');
    $langcode = $this->langcodes[1];
    $entity->langcode->value = $langcode;
    $entity->save();
    $this->assertFieldStorageLangcode($entity, 'Field language successfully changed.');
    $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
    $entity->langcode->value = $langcode;
    $entity->save();
    $this->assertFieldStorageLangcode($entity, 'Field language successfully changed to language neutral.');

    // Test that after switching field translatability things keep working as
    // before.
    $this->toggleFieldTranslatability($entity_type, $entity_type);
    $entity = $this->reloadEntity($entity);
    foreach ([$this->fieldName, $this->untranslatableFieldName] as $field_name) {
      $this->assertEquals($values[$field_name], $entity->get($field_name)->value, 'Field language works as expected after switching translatability.');
    }

    // Test that after disabling field translatability translated values are not
    // loaded.
    $this->toggleFieldTranslatability($entity_type, $entity_type);
    $entity = $this->reloadEntity($entity);
    $entity->langcode->value = $this->langcodes[0];
    $translation = $entity->addTranslation($this->langcodes[1]);
    $translated_value = $this->randomMachineName();
    $translation->get($this->fieldName)->value = $translated_value;
    $translation->save();
    $this->toggleFieldTranslatability($entity_type, $entity_type);
    $entity = $this->reloadEntity($entity);
    $this->assertEquals($values[$this->fieldName], $entity->getTranslation($this->langcodes[1])->get($this->fieldName)->value, 'Existing field translations are not loaded for untranslatable fields.');
  }

  /**
   * Checks whether field languages are correctly stored for the given entity.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity fields are attached to.
   * @param string $message
   *   (optional) A message to display with the assertion.
   *
   * @internal
   */
  protected function assertFieldStorageLangcode(FieldableEntityInterface $entity, string $message = ''): void {
    $status = TRUE;
    $entity_type = $entity->getEntityTypeId();
    $id = $entity->id();
    $langcode = $entity->getUntranslated()->language()->getId();
    $fields = [$this->fieldName, $this->untranslatableFieldName];
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = \Drupal::entityTypeManager()->getStorage($entity_type)->getTableMapping();

    foreach ($fields as $field_name) {
      $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
      $table = $table_mapping->getDedicatedDataTableName($field_storage);

      $record = \Drupal::database()
        ->select($table, 'f')
        ->fields('f')
        ->condition('f.entity_id', $id)
        ->condition('f.revision_id', $id)
        ->execute()
        ->fetchObject();

      if ($record->langcode != $langcode) {
        $status = FALSE;
        break;
      }
    }

    $this->assertTrue($status, $message);
  }

}
