<?php

namespace Drupal\Tests\lingotek\Functional;

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\lingotek\Exception\LingotekDocumentLockedException;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\node\Entity\Node;

/**
 * Tests the Lingotek content service saves data to entities correctly.
 *
 * @group lingotek
 */
class LingotekSaveTargetDataTest extends LingotekTestBase {

  /**
   * Modules to install.
   *
   * @var array
   */
  protected static $modules = ['node'];

  protected function setUp(): void {
    parent::setUp();

    // Login as admin.
    $this->drupalLogin($this->rootUser);

    // Create Article node type.
    $this->drupalCreateContentType([
      'type' => 'article',
      'name' => 'Article',
    ]);

    // Add languages.
    ConfigurableLanguage::createFromLangcode('es')
      ->setThirdPartySetting('lingotek', 'locale', 'es_ES')
      ->save();
    ConfigurableLanguage::createFromLangcode('de')
      ->setThirdPartySetting('lingotek', 'locale', 'de_DE')
      ->save();

    // Enable translation for the current entity type and ensure the change is
    // picked up.
    ContentLanguageSettings::loadByEntityTypeBundle('node', 'article')->setLanguageAlterable(TRUE)->save();
    \Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);

    drupal_static_reset();
    \Drupal::entityTypeManager()->clearCachedDefinitions();
    $this->applyEntityUpdates();
    // Rebuild the container so that the new languages are picked up by services
    // that hold a list of languages.
    $this->rebuildContainer();

    $this->saveLingotekContentTranslationSettingsForNodeTypes();
  }

  public function testRightNodeIsSavedIfThereIsNoRevisionInMetadata() {
    // Create a node.
    /** @var \Drupal\node\NodeInterface $node */
    $node1 = $this->createNode([
      'type' => 'article',
      'title' => 'Node 1',
    ]);
    $node1->save();

    $node2 = $this->createNode([
      'type' => 'article',
      'title' => 'Node 2',
    ]);
    $node2->save();

    /** @var \Drupal\lingotek\LingotekContentTranslationServiceInterface $translation_service */
    $translation_service = \Drupal::service('lingotek.content_translation');

    $es_data = [
      'title' => [0 => ['value' => 'Nodo 2 ES']],
      'body' => [0 => ['value' => 'es body']],
      '_lingotek_metadata' => [
        '_entity_type_id' => 'node',
        '_entity_id' => 2,
      ],
    ];
    $translation_service->saveTargetData($node2, 'es', $es_data);

    $nodeUntranslated = \Drupal::entityTypeManager()->getStorage('node')->load(1);
    $this->assertFalse($nodeUntranslated->hasTranslation('es'));

    $nodeTranslated = \Drupal::entityTypeManager()->getStorage('node')->load(2);
    $this->assertTrue($nodeTranslated->hasTranslation('es'));
  }

  public function testRightRevisionsAreSavedIfThereIsMetadata() {
    // Create a node.
    /** @var \Drupal\node\NodeInterface $node */
    $node = $this->createNode([
      'type' => 'article',
      'title' => 'Revision 1',
    ]);

    // Create a new revision.
    $node->setTitle('Revision 2');
    $node->setNewRevision();
    $node->save();

    // Create a third one.
    $node->setTitle('Revision 3');
    $node->setNewRevision();
    $node->save();

    /** @var \Drupal\lingotek\LingotekContentTranslationServiceInterface $translation_service */
    $translation_service = \Drupal::service('lingotek.content_translation');

    $es_data = [
      'title' => [0 => ['value' => 'Revision 2 ES']],
      'body' => [0 => ['value' => 'es body']],
      '_lingotek_metadata' => [
        '_entity_type_id' => 'node',
        '_entity_id' => 1,
        '_entity_revision' => 2,
      ],
    ];
    $translation_service->saveTargetData($node, 'es', $es_data);

    $node = \Drupal::entityTypeManager()->getStorage('node')->load(1);
    $node = $node->getTranslation('es');

    $this->assertEquals('es body', $node->body->value, 'The body is translated correctly.');
    $this->assertEquals('Revision 2 ES', $node->getTitle(), 'The title in the revision translation is the one given.');
    $this->assertEquals(3, $node->getRevisionId(), 'The translation is saved in the newest revision.');
  }

  public function testUnderscoreMetadataDoesNotStopFollowingFieldsFromBeingSaved() {
    /** @var \Drupal\node\NodeInterface $node */
    $node = $this->createNode([
      'type' => 'article',
      'title' => 'English title',
      'body' => [
        'value' => 'English body',
        'format' => 'plain_text',
      ],
    ]);
    $node->save();

    /** @var \Drupal\lingotek\LingotekContentTranslationServiceInterface $translation_service */
    $translation_service = \Drupal::service('lingotek.content_translation');

    $es_data = [
      '_lingotek_metadata' => [
        '_entity_type_id' => 'node',
        '_entity_id' => $node->id(),
        '_entity_revision' => $node->getRevisionId(),
      ],
      'title' => [0 => ['value' => 'Título español']],
      'body' => [0 => ['value' => 'Cuerpo español']],
    ];

    $translation_service->saveTargetData($node, 'es', $es_data);

    $node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
    $translation = $node->getTranslation('es');

    $this->assertEquals('Título español', $translation->getTitle());
    $this->assertEquals('Cuerpo español', $translation->body->value);
  }

  public function testSaveTargetDataDoesNotRollbackLiveNonTranslatableFieldFromUploadedRevision() {
    $this->addNonTranslatableArticleField('field_shared_marker');

    /** @var \Drupal\node\NodeInterface $node */
    $node = $this->createNode([
      'type' => 'article',
      'title' => 'Revision 1',
      'body' => [
        'value' => 'English body revision 1',
        'format' => 'plain_text',
      ],
      'field_shared_marker' => 'shared revision 1',
    ]);
    $node->setNewRevision(TRUE);
    $node->save();
    $uploaded_revision_id = $node->getRevisionId();

    $node->setTitle('Revision 2');
    $node->set('field_shared_marker', 'shared live revision 2');
    $node->setNewRevision(TRUE);
    $node->save();

    /** @var \Drupal\lingotek\LingotekContentTranslationServiceInterface $translation_service */
    $translation_service = \Drupal::service('lingotek.content_translation');

    $es_data = [
      'title' => [0 => ['value' => 'Título español']],
      'body' => [0 => ['value' => 'Cuerpo español']],
      '_lingotek_metadata' => [
        '_entity_type_id' => 'node',
        '_entity_id' => $node->id(),
        '_entity_revision' => $uploaded_revision_id,
      ],
    ];

    $translation_service->saveTargetData($node, 'es', $es_data);

    $node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());

    $this->assertEquals('shared live revision 2', $node->get('field_shared_marker')->value);
    $this->assertEquals('Título español', $node->getTranslation('es')->getTitle());
    $this->assertEquals('Cuerpo español', $node->getTranslation('es')->body->value);
  }

  public function testUpdateDocumentWritesNextDocumentIdWhenDocumentIsLocked() {
    /** @var \Drupal\node\NodeInterface $node */
    $node = $this->createNode([
      'type' => 'article',
      'title' => 'English title',
      'body' => [
        'value' => 'English body',
        'format' => 'plain_text',
      ],
    ]);
    $node->save();

    /** @var \Drupal\lingotek\LingotekContentTranslationServiceInterface $translation_service */
    $translation_service = \Drupal::service('lingotek.content_translation');
    $translation_service->setDocumentId($node, 'old-doc-id');

    \Drupal::state()->set('lingotek.must_document_locked_error_in_update', TRUE);

    try {
      $translation_service->updateDocument($node);
      $this->fail('Expected a locked document exception to be rethrown.');
    }
    catch (LingotekDocumentLockedException $exception) {
      $this->assertEquals('new-doc-id', $exception->getNewDocumentId());
    }

    $node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
    $this->assertEquals('new-doc-id', $translation_service->getDocumentId($node));
  }

  public function testFieldsAreNotExtractedIfNotTranslatableEvenIfStorageIsTranslatable() {
    // Ensure field storage is translatable.
    $field_storage = FieldStorageConfig::loadByName('node', 'body');
    $field_storage->setTranslatable(TRUE)->save();

    // Ensure field instance is translatable.
    $field = FieldConfig::loadByName('node', 'article', 'body');
    $field->setTranslatable(TRUE)->save();

    // Ensure changes were saved correctly.
    $field_storage = FieldStorageConfig::loadByName('node', 'body');
    $field = FieldConfig::loadByName('node', 'article', 'body');
    $this->assertTrue($field_storage->isTranslatable(), 'Field storage is translatable.');
    $this->assertTrue($field->isTranslatable(), 'Field instance is translatable.');

    // Create a node.
    $this->createNode([
      'type' => 'article',
    ]);

    $node = Node::load(1);
    $title = $node->getTranslation('en')->getTitle();
    $body = $node->getTranslation('en')->body->value;

    /** @var \Drupal\lingotek\LingotekContentTranslationServiceInterface $translation_service */
    $translation_service = \Drupal::service('lingotek.content_translation');

    $es_data = [
      'title' => [0 => ['value' => 'es title']],
      'body' => [0 => ['value' => 'es body']],
    ];

    $node = $translation_service->saveTargetData($node, 'es', $es_data);

    $this->assertEquals('es body', $node->getTranslation('es')->body->value, 'The body is translated if the field is translatable.');
    $this->assertEquals($body, $node->getTranslation('en')->body->value, 'The body in the original language is not overridden.');
    $this->assertEquals('es title', $node->getTranslation('es')->getTitle(), 'The title in the translation is the one given.');
    $this->assertEquals($title, $node->getTranslation('en')->getTitle(), 'The title in the original language is not overridden.');

    // Make the field as not translatable.
    $field->setTranslatable(FALSE)->save();
    $this->assertTrue($field_storage->isTranslatable(), 'Field storage is translatable.');
    $this->assertFalse($field->isTranslatable(), 'Field instance is not translatable.');

    $de_data = [
      'title' => [0 => ['value' => 'de title']],
      'body' => [0 => ['value' => 'de body']],
    ];
    // If the field is not translatable, the field is not there.
    $node = $translation_service->saveTargetData($node, 'de', $de_data);

    $this->assertEquals($body, $node->getTranslation('de')->body->value, 'The body is not written if the field is not translatable.');
    $this->assertEquals($body, $node->getTranslation('en')->body->value, 'The body is not overridden if the field is not translatable.');
    $this->assertEquals('de title', $node->getTranslation('de')->getTitle(), 'The title in the translation is the one given.');
    $this->assertEquals($title, $node->getTranslation('en')->getTitle(), 'The title in the original language is not overridden.');
  }

  /**
   * Adds a non-translatable text field to the article content type.
   */
  protected function addNonTranslatableArticleField($field_name) {
    FieldStorageConfig::create([
      'field_name' => $field_name,
      'entity_type' => 'node',
      'type' => 'string',
      'cardinality' => 1,
      'translatable' => FALSE,
    ])->save();

    FieldConfig::create([
      'field_name' => $field_name,
      'entity_type' => 'node',
      'bundle' => 'article',
      'label' => $field_name,
      'translatable' => FALSE,
    ])->save();

    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
  }

}
