<?php

declare(strict_types=1);

namespace Drupal\Tests\acquia_dam\Kernel\Service;

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\acquia_dam\Kernel\AcquiaDamKernelTestBase;
use Drupal\acquia_dam\Service\Y2038CompatibilityService;

/**
 * Kernel tests for Y2038CompatibilityService.
 *
 * @group acquia_dam
 * @coversDefaultClass \Drupal\acquia_dam\Service\Y2038CompatibilityService
 */
class Y2038CompatibilityServiceTest extends AcquiaDamKernelTestBase {

  /**
   * The Y2038 compatibility service under test.
   *
   * @var \Drupal\acquia_dam\Service\Y2038CompatibilityService
   */
  protected Y2038CompatibilityService $y2038Service;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->y2038Service = $this->container->get('acquia_dam.y2038_compatibility');
  }

  /**
   * Tests updateExpiryDateFieldToBigint method.
   *
   * @covers ::updateExpiryDateFieldToBigint
   */
  public function testUpdateExpiryDateFieldToBigint(): void {
    $schema = $this->container->get('database')->schema();

    if (!$schema->tableExists('media__acquia_dam_expiry_date')) {
      $this->markTestSkipped('Expiry date field table does not exist');
    }

    // Run the update - changeField() is idempotent.
    $this->y2038Service->updateExpiryDateFieldToBigint();

    // Verify the method completes without errors.
    $this->assertTrue($schema->tableExists('media__acquia_dam_expiry_date'));
  }

  /**
   * Helper to create a field on a media type.
   *
   * @param string $field_name
   *   The machine name of the field to create.
   * @param string $bundle
   *   The media bundle to attach the field to.
   * @param string $type
   *   The field type (e.g. 'timestamp', 'datetime', 'string').
   * @param string $label
   *   The human-readable label for the field.
   */
  protected function createField(string $field_name, string $bundle, string $type, string $label = 'Test Field'): void {
    FieldStorageConfig::create([
      'field_name' => $field_name,
      'entity_type' => 'media',
      'type' => $type,
    ])->save();

    FieldConfig::create([
      'field_storage' => FieldStorageConfig::loadByName('media', $field_name),
      'bundle' => $bundle,
      'label' => $label,
    ])->save();
  }

  /**
   * Tests ensureBigintForMappedDateFields with timestamp field.
   *
   * @covers ::ensureBigintForMappedDateFields
   */
  public function testEnsureBigintForMappedDateFields(): void {
    $media_type = $this->createImageMediaType();
    $field_name = 'field_test_expiry';

    // Create timestamp field.
    $this->createField($field_name, $media_type->id(), 'timestamp');

    // Verify table exists.
    $schema = $this->container->get('database')->schema();
    $this->assertTrue($schema->tableExists("media__{$field_name}"));

    // Map to DAM expiration_date field.
    $media_type->setFieldMap(['expiration_date' => $field_name]);

    // Run conversion with empty array - should process all mapped fields.
    $field_names = $this->y2038Service->findFieldsNeedingBigintConversion($media_type, FALSE);
    $updated = $this->y2038Service->ensureBigintForMappedDateFields($media_type, $field_names, FALSE);
    $this->assertEquals(1, $updated);

    // Run again with specific field name - changeField() is idempotent.
    $updated_again = $this->y2038Service->ensureBigintForMappedDateFields($media_type, [$field_name], FALSE);
    $this->assertEquals(1, $updated_again, 'Method should process specified timestamp fields');
  }

  /**
   * Data provider for field skip tests.
   */
  public static function fieldSkipProvider(): array {
    return [
      'datetime field (VARCHAR)' => [
        'field_type' => 'datetime',
        'dam_field' => 'expiration_date',
        'reason' => 'datetime uses VARCHAR',
      ],
      'non-date DAM field' => [
        'field_type' => 'string',
        'dam_field' => 'title',
        'reason' => 'not a date field',
      ],
    ];
  }

  /**
   * Tests that various field types are correctly skipped.
   *
   * @param string $field_type
   *   The field type to test.
   * @param string $dam_field
   *   The DAM field being mapped to.
   * @param string $reason
   *   The reason why this field should be skipped (for assertion message).
   *
   * @dataProvider fieldSkipProvider
   * @covers ::ensureBigintForMappedDateFields
   */
  public function testSkipsInvalidFields(string $field_type, string $dam_field, string $reason): void {
    $media_type = $this->createImageMediaType();
    $field_name = 'field_test_skip';

    // Create field based on type.
    $this->createField($field_name, $media_type->id(), $field_type);

    // Map to DAM field.
    $media_type->setFieldMap([$dam_field => $field_name]);

    // Run conversion - should skip.
    $updated = $this->y2038Service->ensureBigintForMappedDateFields($media_type, [], FALSE);
    $this->assertEquals(0, $updated, "Should skip: {$reason}");
  }

  /**
   * Tests that unmapped fields are skipped.
   *
   * @covers ::ensureBigintForMappedDateFields
   */
  public function testSkipsUnmappedFields(): void {
    $media_type = $this->createImageMediaType();
    $media_type->setFieldMap([]);

    $updated = $this->y2038Service->ensureBigintForMappedDateFields($media_type, [], FALSE);
    $this->assertEquals(0, $updated);
  }

  /**
   * Tests revision table is also converted.
   *
   * @covers ::ensureBigintForMappedDateFields
   */
  public function testConvertsRevisionTable(): void {
    $media_type = $this->createImageMediaType();
    $field_name = 'field_test_revision';

    $this->createField($field_name, $media_type->id(), 'timestamp');
    $media_type->setFieldMap(['expiration_date' => $field_name]);

    $this->y2038Service->ensureBigintForMappedDateFields($media_type, [], FALSE);

    // Verify revision table exists and field was processed.
    $schema = $this->container->get('database')->schema();
    $revision_table = "media_revision__{$field_name}";
    if ($schema->tableExists($revision_table)) {
      // If revision table exists, verify the column exists (conversion happened).
      $this->assertTrue($schema->fieldExists($revision_table, "{$field_name}_value"));
    }
  }

  /**
   * Tests findFieldsNeedingBigintConversion method.
   *
   * @covers ::findFieldsNeedingBigintConversion
   */
  public function testFindFieldsNeedingBigintConversion(): void {
    $media_type = $this->createImageMediaType();

    // Create multiple fields.
    $timestamp_field = 'field_test_timestamp';
    $datetime_field = 'field_test_datetime';
    $string_field = 'field_test_string';

    $this->createField($timestamp_field, $media_type->id(), 'timestamp');
    $this->createField($datetime_field, $media_type->id(), 'datetime');
    $this->createField($string_field, $media_type->id(), 'string');

    // Map fields to DAM.
    $media_type->setFieldMap([
      'expiration_date' => $timestamp_field,
      'release_date' => $datetime_field,
      'title' => $string_field,
    ]);

    // Find all mapped timestamp fields.
    $fields = $this->y2038Service->findFieldsNeedingBigintConversion($media_type, FALSE);

    // Should only return the timestamp field.
    $this->assertCount(1, $fields);
    $this->assertContains($timestamp_field, $fields);
    $this->assertNotContains($datetime_field, $fields);
    $this->assertNotContains($string_field, $fields);
  }

  /**
   * Tests findFieldsNeedingBigintConversion with only_changed flag.
   *
   * @covers ::findFieldsNeedingBigintConversion
   */
  public function testFindFieldsNeedingBigintConversionOnlyChanged(): void {
    $media_type = $this->createImageMediaType();

    // Create fields.
    $field1 = 'field_test_timestamp1';
    $field2 = 'field_test_timestamp2';

    $this->createField($field1, $media_type->id(), 'timestamp');
    $this->createField($field2, $media_type->id(), 'timestamp');

    // Initial mapping.
    $media_type->setFieldMap([
      'expiration_date' => $field1,
    ]);
    $media_type->save();

    // Load and update mapping.
    $media_type = $this->entityTypeManager->getStorage('media_type')->load($media_type->id());
    $original_map = ['expiration_date' => $field1];
    $media_type->setFieldMap([
      'expiration_date' => $field1,  // Unchanged.
      'release_date' => $field2,      // New mapping.
    ]);

    // Find only changed fields.
    $fields = $this->y2038Service->findFieldsNeedingBigintConversion($media_type, TRUE, $original_map);

    // Should only return the newly mapped field.
    $this->assertCount(1, $fields);
    $this->assertContains($field2, $fields);
    $this->assertNotContains($field1, $fields, 'Unchanged mapping should be skipped');
  }

}
