<?php

namespace Drupal\nodehive_mcp\Plugin\Mcp;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\mcp\Attribute\Mcp;
use Drupal\mcp\Plugin\McpPluginBase;
use Drupal\mcp\ServerFeatures\Tool;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * MCP plugin for creating nodes in NodeHive Spaces.
 */
#[Mcp(
  id: 'create-node-tool',
  name: new TranslatableMarkup('Create Node Tool'),
  description: new TranslatableMarkup('Provides MCP tool for creating nodes in NodeHive Spaces.'),
)]
class CreateNodeTool extends McpPluginBase implements ContainerFactoryPluginInterface {

  use StringTranslationTrait;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUserAccount;

  /**
   * The content translation manager.
   *
   * @var \Drupal\content_translation\ContentTranslationManagerInterface
   */
  protected $contentTranslationManager;

  /**
   * {@inheritDoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ) {
    $instance = parent::create(
      $container, $configuration, $plugin_id, $plugin_definition
    );
    $instance->entityTypeManager = $container->get('entity_type.manager');
    $instance->moduleHandler = $container->get('module_handler');
    $instance->entityFieldManager = $container->get('entity_field.manager');
    $instance->languageManager = $container->get('language_manager');
    $instance->currentUserAccount = $container->get('current_user');
    if ($container->has('content_translation.manager')) {
      $instance->contentTranslationManager = $container->get('content_translation.manager');
    }

    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration(): array {
    return [
      'enabled' => TRUE,
      'config' => [],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(
    array $form,
    FormStateInterface $form_state,
  ): array {
    // No specific configuration needed for this tool.
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function getTools(): array {
    return [
      new Tool(
        name: 'create-node-in-space',
        description: 'Create a new node/content item in a specific NodeHive Space. This tool supports all custom fields defined on the content type and multilingual content creation. Use the explore-content-type-tool first to understand the available fields and languages for your content type.',
        inputSchema: [
          'type' => 'object',
          'properties' => [
            'space_id' => [
              'type' => 'integer',
              'description' => 'The ID of the space to create the node in.',
            ],
            'title' => [
              'type' => 'string',
              'description' => 'The title of the node.',
            ],
            'content_type' => [
              'type' => 'string',
              'description' => 'The content type/bundle of the node (e.g., article, page, markdown_page).',
            ],
            'langcode' => [
              'type' => 'string',
              'description' => 'The language code for the content (e.g., "en", "de", "fr"). If not specified, uses the site default language. Use explore-content-type-tool to see available languages.',
            ],
            'status' => [
              'type' => 'boolean',
              'description' => 'Whether the node should be published.',
              'default' => FALSE,
            ],
            'author_id' => [
              'type' => 'integer',
              'description' => 'The user ID of the author (optional, defaults to current user).',
            ],
            'fields' => [
              'type' => 'object',
              'description' => 'Custom field values for the node. Field names should match the machine names from the content type (e.g., "field_markdown", "body", "field_custom_text"). For text fields, provide a string value. For entity references, provide the target entity ID(s). For formatted text fields, you can provide either a string (uses default format) or an object with "value" and "format" properties.',
              'additionalProperties' => TRUE,
              'examples' => [
                [
                  'body' => [
                    'value' => 'Main content text',
                    'format' => 'basic_html',
                  ],
                  'field_markdown' => '# Markdown Content\n\nThis is **bold** text.',
                  'field_tags' => [1, 2, 3],
                  'field_image' => 123,
                ],
              ],
            ],
          ],
          'required' => ['space_id', 'title', 'content_type'],
        ],
      ),
      new Tool(
        name: 'update-node-in-space',
        description: 'Update an existing node/content item in a NodeHive Space. This tool supports updating all custom fields and can handle multilingual content updates. Use the explore-content-type-tool first to understand the available fields and languages.',
        inputSchema: [
          'type' => 'object',
          'properties' => [
            'node_id' => [
              'type' => 'integer',
              'description' => 'The ID of the node to update.',
            ],
            'langcode' => [
              'type' => 'string',
              'description' => 'The language code for the content to update (e.g., "en", "de", "fr"). If not specified, updates the node in its current language or default language.',
            ],
            'title' => [
              'type' => 'string',
              'description' => 'The new title of the node (optional).',
            ],
            'status' => [
              'type' => 'boolean',
              'description' => 'Whether the node should be published (optional).',
            ],
            'fields' => [
              'type' => 'object',
              'description' => 'Custom field values to update. Field names should match the machine names from the content type (e.g., "field_markdown", "body", "field_custom_text"). Only provided fields will be updated.',
              'additionalProperties' => TRUE,
            ],
          ],
          'required' => ['node_id'],
        ],
      ),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function executeTool(string $toolId, mixed $arguments): array {
    if ($toolId === md5('create-node-in-space')) {
      return $this->executeCreateNodeInSpace($arguments);
    }
    elseif ($toolId === md5('update-node-in-space')) {
      return $this->executeUpdateNodeInSpace($arguments);
    }

    throw new \InvalidArgumentException('Unknown tool: ' . $toolId);
  }

  /**
   * Execute the create node in space tool.
   */
  protected function executeCreateNodeInSpace(array $arguments): array {
    try {
      // Validate required fields.
      if (empty($arguments['space_id'])) {
        throw new \InvalidArgumentException('Space ID is required.');
      }
      if (empty($arguments['title'])) {
        throw new \InvalidArgumentException('Title is required.');
      }
      if (empty($arguments['content_type'])) {
        throw new \InvalidArgumentException('Content type is required.');
      }

      // Validate and prepare language.
      $langcode = $this->validateAndPrepareLangcode($arguments['langcode'] ?? NULL);

      // Load the space.
      $space_storage = $this->entityTypeManager->getStorage('nodehive_space');
      $space = $space_storage->load($arguments['space_id']);

      if (!$space) {
        throw new \InvalidArgumentException('Space with ID ' . $arguments['space_id'] . ' not found.');
      }

      // Check if the content type is allowed in this space.
      $allowed_content_types = [];
      if (!$space->get('content_types')->isEmpty()) {
        foreach ($space->get('content_types')->referencedEntities() as $content_type) {
          $allowed_content_types[] = $content_type->id();
        }

        if (!empty($allowed_content_types) && !in_array($arguments['content_type'], $allowed_content_types)) {
          throw new \InvalidArgumentException(
            sprintf(
              'Content type "%s" is not allowed in space "%s". Allowed types: %s',
              $arguments['content_type'],
              $space->label(),
              implode(', ', $allowed_content_types)
            )
          );
        }
      }

      // Verify the content type exists.
      $node_type_storage = $this->entityTypeManager->getStorage('node_type');
      $node_type = $node_type_storage->load($arguments['content_type']);
      if (!$node_type) {
        throw new \InvalidArgumentException('Content type "' . $arguments['content_type'] . '" does not exist.');
      }

      // Check if content translation is enabled for this content type.
      $translation_enabled = FALSE;
      if ($this->moduleHandler->moduleExists('content_translation') && $this->contentTranslationManager) {
        $translation_enabled = $this->contentTranslationManager->isEnabled('node', $arguments['content_type']);
      }

      // Determine the author.
      $author_id = $arguments['author_id'] ?? $this->currentUserAccount->id();
      if ($author_id && !$this->entityTypeManager->getStorage('user')->load($author_id)) {
        throw new \InvalidArgumentException('Author with ID ' . $author_id . ' does not exist.');
      }

      // Get field definitions for this content type.
      $field_definitions = $this->entityFieldManager->getFieldDefinitions('node', $arguments['content_type']);

      // Create the node with basic data.
      $node_storage = $this->entityTypeManager->getStorage('node');
      $node_data = [
        'type' => $arguments['content_type'],
        'title' => $arguments['title'],
        'status' => $arguments['status'] ?? FALSE,
        'uid' => $author_id,
        'langcode' => $langcode,
        'nodehive_space' => [$arguments['space_id']],
      ];

      // Process custom fields if provided.
      if (!empty($arguments['fields']) && is_array($arguments['fields'])) {
        foreach ($arguments['fields'] as $field_name => $field_value) {
          // Skip if field doesn't exist on this content type.
          if (!isset($field_definitions[$field_name])) {
            continue;
          }

          $field_definition = $field_definitions[$field_name];
          $field_type = $field_definition->getType();

          // Process field value based on field type.
          $processed_value = $this->processFieldValue($field_type, $field_value, $field_definition);
          if ($processed_value !== NULL) {
            $node_data[$field_name] = $processed_value;
          }
        }
      }

      $node = $node_storage->create($node_data);
      $node->save();

      // Prepare success message with field information.
      $field_info = [];
      if (!empty($arguments['fields'])) {
        foreach ($arguments['fields'] as $field_name => $field_value) {
          if (isset($field_definitions[$field_name])) {
            $field_info[] = sprintf('%s: %s', $field_name, $this->summarizeFieldValue($field_value));
          }
        }
      }

      // Get language name for better UX.
      $language_name = $langcode;
      try {
        $language_object = $this->languageManager->getLanguage($langcode);
        if ($language_object) {
          $language_name = $language_object->getName();
        }
      }
      catch (\Exception $e) {
        // Fallback to langcode if language object can't be retrieved.
        $language_name = $langcode;
      }

      $message = sprintf(
        'Successfully created node "%s" (ID: %d) of type "%s" in space "%s" (ID: %d). Language: %s (%s). Status: %s',
        $node->getTitle(),
        $node->id(),
        $arguments['content_type'],
        $space->label(),
        $space->id(),
        $language_name,
        $langcode,
        $node->isPublished() ? 'Published' : 'Unpublished'
      );

      if ($translation_enabled) {
        $message .= sprintf("\n\nTranslation Info: Content translation is enabled for this content type. You can create translations in other languages by creating additional nodes with the same base content and different langcode values.");
      }

      if (!empty($field_info)) {
        $message .= "\n\nFields set:\n• " . implode("\n• ", $field_info);
      }

      return [
        [
          'type' => 'text',
          'text' => $message,
        ],
      ];
    }
    catch (\Exception $e) {
      return [
        [
          'type' => 'text',
          'text' => 'Error creating node: ' . $e->getMessage(),
        ],
      ];
    }
  }

  /**
   * Execute the update node in space tool.
   */
  protected function executeUpdateNodeInSpace(array $arguments): array {
    try {
      // Validate required fields.
      if (empty($arguments['node_id'])) {
        throw new \InvalidArgumentException('Node ID is required.');
      }

      // Load the node.
      $node_storage = $this->entityTypeManager->getStorage('node');
      $node = $node_storage->load($arguments['node_id']);

      if (!$node) {
        throw new \InvalidArgumentException('Node with ID ' . $arguments['node_id'] . ' not found.');
      }

      // Validate and prepare language.
      $langcode = $this->validateAndPrepareLangcode($arguments['langcode'] ?? NULL);

      // Check if we need to work with a translation.
      $translation_enabled = FALSE;
      if ($this->moduleHandler->moduleExists('content_translation') && $this->contentTranslationManager) {
        $translation_enabled = $this->contentTranslationManager->isEnabled('node', $node->bundle());
      }

      // Get the appropriate language version of the node.
      if ($translation_enabled && $node->hasTranslation($langcode)) {
        $node = $node->getTranslation($langcode);
      }
      elseif ($translation_enabled && $langcode !== $node->language()->getId()) {
        // Create a new translation if it doesn't exist.
        if ($node->isTranslatable()) {
          $node = $node->addTranslation($langcode);
        }
        else {
          throw new \InvalidArgumentException(
            sprintf(
              'Node %d does not have a translation for language "%s" and cannot create one.',
              $arguments['node_id'],
              $langcode
            )
                  );
        }
      }

      $content_type = $node->bundle();

      // Get field definitions for validation.
      $field_definitions = $this->entityFieldManager->getFieldDefinitions('node', $content_type);

      // Track what's being updated.
      $updated_fields = [];

      // Update title if provided.
      if (isset($arguments['title'])) {
        $node->setTitle($arguments['title']);
        $updated_fields[] = 'title: ' . $arguments['title'];
      }

      // Update status if provided.
      if (isset($arguments['status'])) {
        $node->setPublished($arguments['status']);
        $updated_fields[] = 'status: ' . ($arguments['status'] ? 'Published' : 'Unpublished');
      }

      // Process custom fields if provided.
      if (!empty($arguments['fields']) && is_array($arguments['fields'])) {
        foreach ($arguments['fields'] as $field_name => $field_value) {
          // Skip if field doesn't exist on this content type.
          if (!isset($field_definitions[$field_name])) {
            continue;
          }

          $field_definition = $field_definitions[$field_name];
          $field_type = $field_definition->getType();

          // Process field value based on field type.
          $processed_value = $this->processFieldValue($field_type, $field_value, $field_definition);
          if ($processed_value !== NULL) {
            $node->set($field_name, $processed_value);
            $updated_fields[] = sprintf('%s: %s', $field_name, $this->summarizeFieldValue($field_value));
          }
        }
      }

      // Save the node.
      $node->save();

      // Get language name for better UX.
      $language_name = $langcode;
      try {
        $language_object = $this->languageManager->getLanguage($langcode);
        if ($language_object) {
          $language_name = $language_object->getName();
        }
      }
      catch (\Exception $e) {
        // Fallback to langcode if language object can't be retrieved.
        $language_name = $langcode;
      }

      $message = sprintf(
        'Successfully updated node "%s" (ID: %d) of type "%s". Language: %s (%s). Status: %s',
        $node->getTitle(),
        $node->id(),
        $content_type,
        $language_name,
        $langcode,
        $node->isPublished() ? 'Published' : 'Unpublished'
      );

      if ($translation_enabled) {
        $available_translations = array_keys($node->getTranslationLanguages());
        $message .= sprintf("\n\nTranslation Info: Available translations for this node: %s", implode(', ', $available_translations));
      }

      if (!empty($updated_fields)) {
        $message .= "\n\nUpdated fields:\n• " . implode("\n• ", $updated_fields);
      }
      else {
        $message .= "\n\nNo fields were updated.";
      }

      return [
        [
          'type' => 'text',
          'text' => $message,
        ],
      ];
    }
    catch (\Exception $e) {
      return [
        [
          'type' => 'text',
          'text' => 'Error updating node: ' . $e->getMessage(),
        ],
      ];
    }
  }

  /**
   * Validate and prepare language code.
   */
  protected function validateAndPrepareLangcode(?string $langcode): string {
    // If no langcode provided, use default language.
    if (empty($langcode)) {
      return $this->languageManager->getDefaultLanguage()->getId();
    }

    // Check if language module is enabled.
    if (!$this->moduleHandler->moduleExists('language')) {
      // If language module is not enabled, only allow default language.
      $default_langcode = $this->languageManager->getDefaultLanguage()->getId();
      if ($langcode !== $default_langcode) {
        throw new \InvalidArgumentException(
          sprintf(
            'Language module is not enabled. Only the default language "%s" is available. Provided: "%s"',
            $default_langcode,
            $langcode
          )
        );
      }
      return $default_langcode;
    }

    // Validate that the language exists.
    $available_languages = $this->languageManager->getLanguages();
    if (!isset($available_languages[$langcode])) {
      $available_langcodes = array_keys($available_languages);
      throw new \InvalidArgumentException(
        sprintf(
          'Language "%s" is not available. Available languages: %s',
          $langcode,
          implode(', ', $available_langcodes)
        )
      );
    }

    return $langcode;
  }

  /**
   * Process a field value based on its type.
   */
  protected function processFieldValue(string $field_type, mixed $field_value, $field_definition): mixed {
    switch ($field_type) {
      case 'string':
      case 'string_long':
        return is_string($field_value) ? $field_value : (string) $field_value;

      case 'text':
      case 'text_long':
      case 'text_with_summary':
        if (is_string($field_value)) {
          return [
            'value' => $field_value,
            'format' => 'basic_html',
          ];
        }
        elseif (is_array($field_value) && isset($field_value['value'])) {
          return [
            'value' => $field_value['value'],
            'format' => $field_value['format'] ?? 'basic_html',
          ];
        }
        break;

      case 'integer':
        return is_numeric($field_value) ? (int) $field_value : NULL;

      case 'decimal':
      case 'float':
        return is_numeric($field_value) ? (float) $field_value : NULL;

      case 'boolean':
        return (bool) $field_value;

      case 'entity_reference':
        // Handle single or multiple entity references.
        if (is_array($field_value)) {
          return array_map(function ($id) {
            return ['target_id' => (int) $id];
          }, $field_value);
        }
        elseif (is_numeric($field_value)) {
          return [['target_id' => (int) $field_value]];
        }
        break;

      case 'datetime':
        if (is_string($field_value)) {
          return $field_value;
        }
        break;

      case 'timestamp':
        if (is_numeric($field_value)) {
          return (int) $field_value;
        }
        elseif (is_string($field_value)) {
          return strtotime($field_value);
        }
        break;

      case 'file':
      case 'image':
        if (is_numeric($field_value)) {
          return [['target_id' => (int) $field_value]];
        }
        elseif (is_array($field_value)) {
          return array_map(function ($id) {
            return ['target_id' => (int) $id];
          }, $field_value);
        }
        break;
    }

    return $field_value;
  }

  /**
   * Create a summary of field value for logging.
   */
  protected function summarizeFieldValue(mixed $field_value): string {
    if (is_string($field_value)) {
      return strlen($field_value) > 50 ? substr($field_value, 0, 50) . '...' : $field_value;
    }
    elseif (is_array($field_value)) {
      if (isset($field_value['value'])) {
        $value = strlen($field_value['value']) > 50 ? substr($field_value['value'], 0, 50) . '...' : $field_value['value'];
        return $value . (isset($field_value['format']) ? ' (' . $field_value['format'] . ')' : '');
      }
      return 'Array with ' . count($field_value) . ' items';
    }
    elseif (is_bool($field_value)) {
      return $field_value ? 'true' : 'false';
    }
    else {
      return (string) $field_value;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function hasAccess(): AccessResult {
    return AccessResult::allowedIfHasPermission(
      $this->currentUser, 'use nodehive mcp tools'
    );
  }

  /**
   * {@inheritdoc}
   */
  public function checkRequirements(): bool {
    return $this->moduleHandler->moduleExists('nodehive_core') &&
           $this->moduleHandler->moduleExists('mcp');
  }

}
