<?php

namespace Drupal\Tests\ai_automators\Unit;

use Drupal\ai_automators\Plugin\AiAutomatorType\LlmModerationState;
use Drupal\ai_automators\Plugin\AiAutomatorType\LlmRewriteImageFilename;
use Drupal\ai_automators\Plugin\AiAutomatorType\LlmVideoToImage;
use Drupal\ai_automators\PluginBaseClasses\FaqField;
use Drupal\ai_automators\PluginBaseClasses\OfficeHours;
use Drupal\ai_automators\PluginBaseClasses\RuleBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use PHPUnit\Framework\TestCase;

/**
 * Tests automator structured output schema generation.
 *
 * @group ai_automators
 */
class StructuredOutputSchemaTest extends TestCase {

  /**
   * Tests the default wrapped schema for a plain string field.
   */
  public function testDefaultSchemaWrapsValues(): void {
    $rule = $this->createRuleBase();
    $field = $this->createFieldDefinition('string');

    $rule->exposeSetStructuredOutputSchema($this->createMock(ContentEntityInterface::class), $field, []);
    $schema = $rule->getJsonSchema();

    $this->assertIsArray($schema);
    $this->assertTrue($schema['strict']);
    $this->assertEquals('object', $schema['schema']['type']);
    $this->assertEquals('array', $schema['schema']['properties']['values']['type']);
    $this->assertEquals('string', $schema['schema']['properties']['values']['items']['properties']['value']['type']);
  }

  /**
   * Tests that list fields derive enums from allowed values.
   */
  public function testListFieldSchemaIncludesEnum(): void {
    $rule = $this->createRuleBase();
    $field = $this->createFieldDefinition('list_string', 'field_list', [
      'allowed_values' => [
        'one' => 'One',
        'two' => 'Two',
      ],
    ]);

    $rule->exposeSetStructuredOutputSchema($this->createMock(ContentEntityInterface::class), $field, []);
    $valueSchema = $rule->getJsonSchema()['schema']['properties']['values']['items']['properties']['value'];

    $this->assertEquals('string', $valueSchema['type']);
    $this->assertContains('one', $valueSchema['enum']);
    $this->assertContains('One', $valueSchema['enum']);
    $this->assertContains('two', $valueSchema['enum']);
    $this->assertContains('Two', $valueSchema['enum']);
  }

  /**
   * Tests link field schema uses uri/title object shape.
   */
  public function testLinkFieldSchema(): void {
    $rule = $this->createRuleBase();
    $field = $this->createFieldDefinition('link', 'field_link', [
      'title' => 2,
    ]);

    $rule->exposeSetStructuredOutputSchema($this->createMock(ContentEntityInterface::class), $field, []);
    $valueSchema = $rule->getJsonSchema()['schema']['properties']['values']['items']['properties']['value'];

    $this->assertEquals('object', $valueSchema['type']);
    $this->assertContains('uri', $valueSchema['required']);
    $this->assertContains('title', $valueSchema['required']);
    $this->assertEquals('string', $valueSchema['properties']['uri']['type']);
  }

  /**
   * Tests link field with optional title exposes title in schema.
   */
  public function testLinkFieldSchemaOptionalTitle(): void {
    $rule = $this->createRuleBase();
    $field = $this->createFieldDefinition('link', 'field_link', [
      'title' => 1,
    ]);

    $rule->exposeSetStructuredOutputSchema($this->createMock(ContentEntityInterface::class), $field, []);
    $valueSchema = $rule->getJsonSchema()['schema']['properties']['values']['items']['properties']['value'];

    $this->assertContains('title', $valueSchema['required']);
    $this->assertArrayHasKey('title', $valueSchema['properties']);
  }

  /**
   * Tests link field with title disabled omits title entirely.
   */
  public function testLinkFieldSchemaNoTitle(): void {
    $rule = $this->createRuleBase();
    $field = $this->createFieldDefinition('link', 'field_link', [
      'title' => 0,
    ]);

    $rule->exposeSetStructuredOutputSchema($this->createMock(ContentEntityInterface::class), $field, []);
    $valueSchema = $rule->getJsonSchema()['schema']['properties']['values']['items']['properties']['value'];

    $this->assertNotContains('title', $valueSchema['required']);
    $this->assertArrayNotHasKey('title', $valueSchema['properties']);
  }

  /**
   * Tests boolean field schema produces bool type.
   */
  public function testBooleanFieldSchema(): void {
    $rule = $this->createRuleBase();
    $field = $this->createFieldDefinition('boolean');

    $rule->exposeSetStructuredOutputSchema($this->createMock(ContentEntityInterface::class), $field, []);
    $valueSchema = $rule->getJsonSchema()['schema']['properties']['values']['items']['properties']['value'];

    $this->assertEquals('boolean', $valueSchema['type']);
  }

  /**
   * Tests FAQ field schema has question and answer string properties.
   */
  public function testFaqFieldSchemaOverride(): void {
    $plugin = $this->createFaqFieldPlugin();
    $field = $this->createFieldDefinition('field_faq');
    $valueSchema = $plugin->exposeValueSchema($this->createMock(ContentEntityInterface::class), $field, []);

    $this->assertEquals('object', $valueSchema['type']);
    $this->assertContains('question', $valueSchema['required']);
    $this->assertContains('answer', $valueSchema['required']);
    $this->assertEquals('string', $valueSchema['properties']['question']['type']);
    $this->assertEquals('string', $valueSchema['properties']['answer']['type']);
  }

  /**
   * Tests video-to-image schema uses nested array with timestamp pattern.
   */
  public function testVideoToImageSchemaOverride(): void {
    $plugin = $this->createVideoToImagePlugin();
    $field = $this->createFieldDefinition('image');
    $valueSchema = $plugin->exposeValueSchema($this->createMock(ContentEntityInterface::class), $field, []);

    $this->assertEquals('array', $valueSchema['type']);
    $this->assertEquals('object', $valueSchema['items']['type']);
    $this->assertContains('timestamp', $valueSchema['items']['required']);
    $this->assertArrayHasKey('pattern', $valueSchema['items']['properties']['timestamp']);
  }

  /**
   * Tests office-hours override schema is strict and patterned.
   */
  public function testOfficeHoursSchemaOverride(): void {
    $plugin = $this->createOfficeHoursPlugin();
    $field = $this->createFieldDefinition('office_hours');
    $valueSchema = $plugin->exposeValueSchema($this->createMock(ContentEntityInterface::class), $field, []);

    $this->assertEquals('object', $valueSchema['type']);
    $this->assertContains('day', $valueSchema['required']);
    $this->assertEquals('^[1-7]$', $valueSchema['properties']['day']['pattern']);
    $this->assertArrayHasKey('pattern', $valueSchema['properties']['starthours']);
    $this->assertArrayHasKey('pattern', $valueSchema['properties']['endhours']);
  }

  /**
   * Tests rewrite-filename override schema includes filename pattern.
   */
  public function testRewriteFilenameSchemaOverride(): void {
    $plugin = $this->createRewriteFilenamePlugin();
    $field = $this->createFieldDefinition('image');
    $valueSchema = $plugin->exposeValueSchema($this->createMock(ContentEntityInterface::class), $field, []);

    $this->assertEquals('object', $valueSchema['type']);
    $this->assertContains('filename', $valueSchema['required']);
    $this->assertEquals('^[a-z0-9-]+$', $valueSchema['properties']['filename']['pattern']);
  }

  /**
   * Tests moderation schema includes enum + optional reasoning.
   */
  public function testModerationSchemaOverride(): void {
    $plugin = $this->createModerationPlugin();
    $field = $this->createFieldDefinition('string', 'moderation_state');
    $valueSchema = $plugin->exposeValueSchema($this->createMock(ContentEntityInterface::class), $field, [
      'store_explanation' => 'field_reasoning',
    ]);

    $this->assertEquals('object', $valueSchema['type']);
    $this->assertContains('state', $valueSchema['required']);
    $this->assertContains('reasoning', $valueSchema['required']);
    $this->assertEquals(['draft', 'published'], $valueSchema['properties']['state']['enum']);
  }

  /**
   * Creates a simple test RuleBase instance.
   */
  protected function createRuleBase(): TestRuleBasePlugin {
    return new TestRuleBasePlugin('test_rule');
  }

  /**
   * Creates an office-hours plugin instance.
   */
  protected function createOfficeHoursPlugin(): TestOfficeHoursPlugin {
    return new TestOfficeHoursPlugin('office_hours');
  }

  /**
   * Creates an image-filename plugin instance.
   */
  protected function createRewriteFilenamePlugin(): TestRewriteFilenamePlugin {
    return new TestRewriteFilenamePlugin('rewrite_filename');
  }

  /**
   * Creates a moderation-state plugin instance.
   */
  protected function createModerationPlugin(): TestModerationStatePlugin {
    return new TestModerationStatePlugin('moderation_state');
  }

  /**
   * Creates a FAQ field plugin instance.
   */
  protected function createFaqFieldPlugin(): TestFaqFieldPlugin {
    return new TestFaqFieldPlugin('faq_field');
  }

  /**
   * Creates a video-to-image plugin instance.
   */
  protected function createVideoToImagePlugin(): TestVideoToImagePlugin {
    return new TestVideoToImagePlugin('video_to_image');
  }

  /**
   * Creates a field definition mock for schema tests.
   *
   * @param string $type
   *   The field type.
   * @param string $name
   *   The field name.
   * @param array<string,mixed> $settings
   *   The field settings.
   */
  protected function createFieldDefinition(string $type, string $name = 'field_value', array $settings = []): FieldDefinitionInterface {
    $field = $this->createMock(FieldDefinitionInterface::class);
    $field->method('getType')->willReturn($type);
    $field->method('getName')->willReturn($name);
    $field->method('getSetting')->willReturnCallback(static fn(string $key) => $settings[$key] ?? NULL);
    return $field;
  }

}

/**
 * Test helper to expose protected RuleBase schema methods.
 */
class TestRuleBasePlugin extends RuleBase {

  /**
   * Lightweight constructor to avoid dependency-heavy parent constructor.
   */
  public function __construct(string $pluginId = 'test_rule') {
    $this->pluginId = $pluginId;
    $this->pluginDefinition = ['label' => 'Test Rule'];
    $this->configuration = [];
    $this->jsonSchema = [];
  }

  /**
   * Exposes schema setup for tests.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being processed.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The field definition.
   * @param array<mixed> $automatorConfig
   *   The automator configuration.
   */
  public function exposeSetStructuredOutputSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): void {
    $this->setStructuredOutputSchema($entity, $fieldDefinition, $automatorConfig);
  }

}

/**
 * Test helper for OfficeHours schema override.
 */
class TestOfficeHoursPlugin extends OfficeHours {

  /**
   * Lightweight constructor to avoid dependency-heavy parent constructor.
   */
  public function __construct(string $pluginId = 'office_hours') {
    $this->pluginId = $pluginId;
    $this->pluginDefinition = ['label' => 'Office Hours'];
    $this->configuration = [];
    $this->jsonSchema = [];
  }

  /**
   * Exposes value schema method for tests.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being processed.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The field definition.
   * @param array<mixed> $automatorConfig
   *   The automator configuration.
   *
   * @return array<mixed>
   *   The value schema.
   */
  public function exposeValueSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): array {
    return $this->getStructuredOutputValueSchema($entity, $fieldDefinition, $automatorConfig);
  }

}

/**
 * Test helper for rewrite-filename schema override.
 */
class TestRewriteFilenamePlugin extends LlmRewriteImageFilename {

  /**
   * Lightweight constructor to avoid dependency-heavy parent constructor.
   */
  public function __construct(string $pluginId = 'rewrite_filename') {
    $this->pluginId = $pluginId;
    $this->pluginDefinition = ['label' => 'Rewrite filename'];
    $this->configuration = [];
    $this->jsonSchema = [];
  }

  /**
   * Exposes value schema method for tests.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being processed.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The field definition.
   * @param array<mixed> $automatorConfig
   *   The automator configuration.
   *
   * @return array<mixed>
   *   The value schema.
   */
  public function exposeValueSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): array {
    return $this->getStructuredOutputValueSchema($entity, $fieldDefinition, $automatorConfig);
  }

}

/**
 * Test helper for FaqField schema override.
 */
class TestFaqFieldPlugin extends FaqField {

  /**
   * Lightweight constructor to avoid dependency-heavy parent constructor.
   */
  public function __construct(string $pluginId = 'faq_field') {
    $this->pluginId = $pluginId;
    $this->pluginDefinition = ['label' => 'FAQ Field'];
    $this->configuration = [];
    $this->jsonSchema = [];
  }

  /**
   * Exposes value schema method for tests.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being processed.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The field definition.
   * @param array<mixed> $automatorConfig
   *   The automator configuration.
   *
   * @return array<mixed>
   *   The value schema.
   */
  public function exposeValueSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): array {
    return $this->getStructuredOutputValueSchema($entity, $fieldDefinition, $automatorConfig);
  }

}

/**
 * Test helper for LlmVideoToImage schema override.
 */
class TestVideoToImagePlugin extends LlmVideoToImage {

  /**
   * Lightweight constructor to avoid dependency-heavy parent constructor.
   */
  public function __construct(string $pluginId = 'video_to_image') {
    $this->pluginId = $pluginId;
    $this->pluginDefinition = ['label' => 'Video To Image'];
    $this->configuration = [];
    $this->jsonSchema = [];
  }

  /**
   * Exposes value schema method for tests.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being processed.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The field definition.
   * @param array<mixed> $automatorConfig
   *   The automator configuration.
   *
   * @return array<mixed>
   *   The value schema.
   */
  public function exposeValueSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): array {
    return $this->getStructuredOutputValueSchema($entity, $fieldDefinition, $automatorConfig);
  }

}

/**
 * Test helper for moderation schema override.
 */
class TestModerationStatePlugin extends LlmModerationState {

  /**
   * Lightweight constructor to avoid dependency-heavy parent constructor.
   */
  public function __construct(string $pluginId = 'moderation_state') {
    $this->pluginId = $pluginId;
    $this->pluginDefinition = ['label' => 'Moderation state'];
    $this->configuration = [];
    $this->jsonSchema = [];
  }

  /**
   * Exposes value schema method for tests.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity being processed.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
   *   The field definition.
   * @param array<mixed> $automatorConfig
   *   The automator configuration.
   *
   * @return array<mixed>
   *   The value schema.
   */
  public function exposeValueSchema(ContentEntityInterface $entity, FieldDefinitionInterface $fieldDefinition, array $automatorConfig): array {
    return $this->getStructuredOutputValueSchema($entity, $fieldDefinition, $automatorConfig);
  }

  /**
   * {@inheritdoc}
   */
  protected function getFlags(ContentEntityInterface $entity) {
    return [
      'draft' => 'Draft',
      'published' => 'Published',
    ];
  }

}
