<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_trigger\Unit\Service;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\flowdrop_node_type\Service\NodeMetadataResolver;
use Drupal\flowdrop_node_type\Service\UiSchemaGenerator;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager;
use Drupal\flowdrop_trigger\FlowDropEventTypeInterface;
use Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface;
use Drupal\flowdrop_trigger\Service\EventTypePluginManager;
use Drupal\flowdrop_trigger\Service\TriggerConfigSchemaBuilder;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * Tests the in-editor schema built by TriggerConfigSchemaBuilder.
 *
 * @coversDefaultClass \Drupal\flowdrop_trigger\Service\TriggerConfigSchemaBuilder
 * @group flowdrop_trigger
 */
class TriggerConfigSchemaBuilderTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * The event type plugin manager mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_trigger\Service\EventTypePluginManager
   */
  private $eventTypeManager;

  /**
   * The entity type manager mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private $entityTypeManager;

  /**
   * The node metadata resolver mock (injects reserved config props).
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_node_type\Service\NodeMetadataResolver
   */
  private $metadataResolver;

  /**
   * The uiSchema generator mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_node_type\Service\UiSchemaGenerator
   */
  private $uiSchemaGenerator;

  /**
   * The schema builder under test.
   *
   * @var \Drupal\flowdrop_trigger\Service\TriggerConfigSchemaBuilder
   */
  private TriggerConfigSchemaBuilder $builder;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->eventTypeManager = $this->prophesize(EventTypePluginManager::class);
    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
    $bundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class);
    $orchestratorManager = $this->prophesize(OrchestratorPluginManager::class);

    // Event type options for the event_type select.
    $this->eventTypeManager->getDefinitions()->willReturn([
      'entity.insert' => ['label' => 'Entity Insert'],
      'entity.presave' => ['label' => 'Entity Presave'],
      'user.login' => ['label' => 'User Login'],
      'form.submit' => ['label' => 'Form Submit'],
      'cron.run' => ['label' => 'Cron Run'],
    ]);

    // No content entity types / roles needed for these assertions.
    $this->entityTypeManager->getDefinitions()->willReturn([]);
    $roleStorage = $this->prophesize(EntityStorageInterface::class);
    $roleStorage->loadMultiple()->willReturn([]);
    $this->entityTypeManager->getStorage('user_role')->willReturn($roleStorage->reveal());

    // Cron schedule field is pulled from the cron.run event type plugin.
    $cronPlugin = $this->prophesize(FlowDropEventTypeInterface::class);
    $cronPlugin->getParameterSchema()->willReturn([
      'type' => 'object',
      'properties' => [
        'cron_schedule' => [
          'type' => 'object',
          'title' => 'Schedule Configuration',
          'properties' => ['mode' => ['type' => 'string']],
        ],
      ],
    ]);
    $this->eventTypeManager->hasDefinition('cron.run')->willReturn(TRUE);
    $this->eventTypeManager->createInstance('cron.run')->willReturn($cronPlugin->reveal());

    $this->metadataResolver = $this->prophesize(NodeMetadataResolver::class);
    $this->uiSchemaGenerator = $this->prophesize(UiSchemaGenerator::class);

    $this->builder = new TriggerConfigSchemaBuilder(
      $this->eventTypeManager->reveal(),
      $this->entityTypeManager->reveal(),
      $bundleInfo->reveal(),
      $orchestratorManager->reveal(),
      $this->metadataResolver->reveal(),
      $this->uiSchemaGenerator->reveal(),
    );
  }

  /**
   * @covers ::buildEditorSchema
   */
  public function testEditorSchemaDefaultsEventTypeToRequestedType(): void {
    $schema = $this->builder->buildEditorSchema('user.login');

    $this->assertSame('object', $schema['type']);
    $this->assertSame(['event_type'], $schema['required']);
    $this->assertSame('user.login', $schema['properties']['event_type']['default']);
  }

  /**
   * @covers ::buildEditorSchema
   */
  public function testUserEventExposesRolesFlatAndNothingElse(): void {
    $props = $this->builder->buildEditorSchema('user.login')['properties'];

    $this->assertArrayHasKey('roles', $props);
    // Condition fields are top-level, not nested under a "conditions" object.
    $this->assertArrayNotHasKey('conditions', $props);
    $this->assertArrayNotHasKey('entity_types', $props);
    $this->assertArrayNotHasKey('cron_schedule', $props);
  }

  /**
   * @covers ::buildEditorSchema
   */
  public function testEntityPresaveAddsTriggerToggles(): void {
    $props = $this->builder->buildEditorSchema('entity.presave')['properties'];

    $this->assertArrayHasKey('entity_types', $props);
    $this->assertArrayHasKey('bundles', $props);
    $this->assertArrayHasKey('trigger_on_new', $props);
    $this->assertArrayHasKey('trigger_on_update', $props);
    $this->assertSame('boolean', $props['trigger_on_new']['type']);
  }

  /**
   * @covers ::buildEditorSchema
   * @covers ::buildCronScheduleField
   */
  public function testCronEventExposesScheduleField(): void {
    $props = $this->builder->buildEditorSchema('cron.run')['properties'];

    $this->assertArrayHasKey('cron_schedule', $props);
    $this->assertArrayHasKey('mode', $props['cron_schedule']['properties']);
  }

  /**
   * @covers ::buildEditorSchema
   */
  public function testMatchingEntityValuesAreBakedInAsDefaults(): void {
    $entity = $this->prophesize(FlowDropTriggerConfigInterface::class);
    $entity->getEventType()->willReturn('user.login');
    $entity->getConditions()->willReturn(['roles' => ['editor', 'admin']]);

    $props = $this->builder->buildEditorSchema('user.login', $entity->reveal())['properties'];

    $this->assertSame(['editor', 'admin'], $props['roles']['default']);
  }

  /**
   * @covers ::buildEditorSchema
   */
  public function testMismatchedEntityDoesNotSeedDefaults(): void {
    // The author switched types: the stored conditions belong to the old one.
    $entity = $this->prophesize(FlowDropTriggerConfigInterface::class);
    $entity->getEventType()->willReturn('entity.insert');
    $entity->getConditions()->willReturn(['entity_types' => ['node']]);
    $entity->getNodeId()->willReturn(Argument::any());

    $props = $this->builder->buildEditorSchema('user.login', $entity->reveal())['properties'];

    // The roles field keeps its own (empty) default, not the old conditions.
    $this->assertSame([], $props['roles']['default']);
  }

  /**
   * @covers ::buildEditorForm
   */
  public function testEditorFormHydratesReservedPropsAndGeneratesUiSchema(): void {
    // The narrow editor schema is hydrated with the reserved config props via
    // the main module (executable: TRUE), then a uiSchema is derived from the
    // hydrated schema. Assert the builder wires those two calls and returns
    // both halves — the injection/generation logic is covered by their own
    // unit tests.
    $hydrated = [
      'type' => 'object',
      'properties' => [
        'event_type' => ['type' => 'string'],
        'title' => ['type' => 'string', 'x-group' => 'general'],
        'maxRetries' => ['type' => 'integer', 'x-group' => 'execution'],
        'ports' => ['type' => 'object', 'x-group' => 'ports'],
      ],
    ];
    $uiSchema = ['type' => 'VerticalLayout', 'elements' => []];

    $this->metadataResolver
      ->injectReservedConfigProperties(Argument::type('array'), TRUE)
      ->willReturn($hydrated)
      ->shouldBeCalledOnce();
    $this->uiSchemaGenerator
      ->generate($hydrated)
      ->willReturn($uiSchema)
      ->shouldBeCalledOnce();

    $form = $this->builder->buildEditorForm('user.login');

    $this->assertSame($hydrated, $form['schema']);
    $this->assertSame($uiSchema, $form['uiSchema']);
  }

}
