<?php

namespace Drupal\eb_ui\Controller;

use Drupal\Core\Access\CsrfRequestHeaderAccessCheck;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\eb\PluginInterfaces\PreviewableOperationInterface;
use Drupal\eb\Service\OperationBuilder;
use Drupal\eb\Service\OperationDataBuilderInterface;
use Drupal\eb\Service\PreviewGenerator;
use Drupal\eb\Service\ValidationManager;
use Drupal\eb\Service\YamlParser;
use Drupal\field\FieldConfigInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

/**
 * API controller for Entity Builder UI shared endpoints.
 *
 * This controller handles shared API requests from Entity Builder UI.
 * These endpoints are used by both the YAML editor and grid providers.
 */
class EbUiApiController extends ControllerBase {

  /**
   * Maximum content size in bytes (5 MB).
   */
  protected const MAX_CONTENT_SIZE = 5242880;

  /**
   * Maximum JSON nesting depth.
   */
  protected const MAX_JSON_DEPTH = 10;

  /**
   * Constructor.
   *
   * @param \Drupal\eb\Service\YamlParser $yamlParser
   *   The YAML parser service.
   * @param \Drupal\eb\Service\OperationBuilder $operationBuilder
   *   The operation builder service.
   * @param \Drupal\eb\Service\ValidationManager $validationManager
   *   The validation manager service.
   * @param \Drupal\eb\Service\PreviewGenerator $previewGenerator
   *   The preview generator service.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   The entity field manager service.
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrfTokenGenerator
   *   The CSRF token generator service.
   * @param \Drupal\eb\Service\OperationDataBuilderInterface $operationDataBuilder
   *   The operation data builder service.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundleInfo
   *   The entity type bundle info service.
   */
  public function __construct(
    protected YamlParser $yamlParser,
    protected OperationBuilder $operationBuilder,
    protected ValidationManager $validationManager,
    protected PreviewGenerator $previewGenerator,
    protected EntityFieldManagerInterface $entityFieldManager,
    protected CsrfTokenGenerator $csrfTokenGenerator,
    protected OperationDataBuilderInterface $operationDataBuilder,
    protected EntityTypeBundleInfoInterface $bundleInfo,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    // @phpstan-ignore new.static
    return new static(
      $container->get('eb.yaml_parser'),
      $container->get('eb.operation_builder'),
      $container->get('eb.validation_manager'),
      $container->get('eb.preview_generator'),
      $container->get('entity_field.manager'),
      $container->get('csrf_token'),
      $container->get('eb.operation_data_builder'),
      $container->get('entity_type.bundle.info'),
    );
  }

  /**
   * Validate AJAX request for CSRF protection.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param bool $requireCsrfToken
   *   Whether to require CSRF token validation. Default TRUE for POST requests.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse|null
   *   Error response if validation fails, NULL if valid.
   */
  protected function validateAjaxRequest(Request $request, bool $requireCsrfToken = TRUE): ?JsonResponse {
    // Check X-Requested-With header for AJAX requests.
    if ($request->headers->get('X-Requested-With') !== 'XMLHttpRequest') {
      return new JsonResponse([
        'valid' => FALSE,
        'success' => FALSE,
        'errors' => ['Invalid request: Missing X-Requested-With header'],
      ], 403);
    }

    // Validate user session.
    if ($this->currentUser()->isAnonymous()) {
      return new JsonResponse([
        'valid' => FALSE,
        'success' => FALSE,
        'errors' => ['Invalid request: Authentication required'],
      ], 403);
    }

    // Validate CSRF token for state-changing requests.
    if ($requireCsrfToken) {
      $csrf_token = $request->headers->get('X-CSRF-Token');
      if (!$csrf_token || !$this->csrfTokenGenerator->validate($csrf_token, CsrfRequestHeaderAccessCheck::TOKEN_KEY)) {
        return new JsonResponse([
          'valid' => FALSE,
          'success' => FALSE,
          'errors' => ['Invalid request: CSRF token validation failed'],
        ], 403);
      }
    }

    // Check content size.
    $content_length = $request->headers->get('Content-Length', '0');
    if ((int) $content_length > self::MAX_CONTENT_SIZE) {
      return new JsonResponse([
        'valid' => FALSE,
        'success' => FALSE,
        'errors' => ['Content exceeds maximum allowed size'],
      ], 413);
    }

    return NULL;
  }

  /**
   * Validate JSON structure depth.
   *
   * @param mixed $data
   *   The data to check.
   * @param int $current_depth
   *   Current nesting depth.
   *
   * @return bool
   *   TRUE if depth is within limits.
   */
  protected function validateJsonDepth(mixed $data, int $current_depth = 0): bool {
    if ($current_depth > self::MAX_JSON_DEPTH) {
      return FALSE;
    }

    if (is_array($data)) {
      foreach ($data as $value) {
        if (!$this->validateJsonDepth($value, $current_depth + 1)) {
          return FALSE;
        }
      }
    }

    return TRUE;
  }

  /**
   * API endpoint for validating Entity Builder definition content.
   *
   * Route: POST /eb/api/validate
   * Permission: 'import entity architecture'
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The HTTP request containing the content to validate.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with validation results.
   */
  public function validate(Request $request): JsonResponse {
    // Validate CSRF protection.
    $csrf_error = $this->validateAjaxRequest($request);
    if ($csrf_error !== NULL) {
      return $csrf_error;
    }

    $content = $request->getContent();

    if (empty($content)) {
      return new JsonResponse([
        'valid' => FALSE,
        'errors' => ['No content provided'],
      ], 400);
    }

    try {
      // First, try to decode as JSON to check if it's grid data format.
      $raw_data = json_decode($content, TRUE);

      if (is_array($raw_data) && $this->operationDataBuilder->isDefinitionFormat($raw_data)) {
        // This is definition data - convert directly.
        if (!$this->validateJsonDepth($raw_data)) {
          return new JsonResponse([
            'valid' => FALSE,
            'errors' => ['Content exceeds maximum nesting depth'],
          ], 400);
        }

        $operation_data = $this->operationDataBuilder->build($raw_data);

        // If grid data format with no data, treat as valid (empty form).
        if (empty($operation_data)) {
          return new JsonResponse([
            'valid' => TRUE,
            'operation_count' => 0,
            'message' => 'No operations to validate',
          ]);
        }
      }
      else {
        // Check if content is valid YAML.
        if (!$this->yamlParser->validateContent($content)) {
          return new JsonResponse([
            'valid' => FALSE,
            'errors' => ['Invalid YAML content'],
          ], 400);
        }

        // Parse content.
        $parsed_data = $this->yamlParser->parse($content);

        // Validate JSON depth to prevent DoS attacks.
        if (!$this->validateJsonDepth($parsed_data)) {
          return new JsonResponse([
            'valid' => FALSE,
            'errors' => ['Content exceeds maximum nesting depth'],
          ], 400);
        }

        // Convert definition data to operation arrays if needed.
        if ($this->operationDataBuilder->isDefinitionFormat($parsed_data)) {
          $operation_data = $this->operationDataBuilder->build($parsed_data);
        }
        else {
          // Data is already in operation format (has 'operation' keys).
          $operation_data = $parsed_data;
        }
      }

      // Build operations.
      $operations = $this->operationBuilder->buildBatch($operation_data);

      if (empty($operations)) {
        return new JsonResponse([
          'valid' => FALSE,
          'errors' => ['No valid operations could be built from the provided data'],
        ]);
      }

      // Validate operations using batch validation.
      $batch_result = $this->validationManager->validateBatch($operations);

      if (!$batch_result->isValid()) {
        $errors = [];
        foreach ($batch_result->getErrors() as $error) {
          $errors[] = $error['message'];
        }
        return new JsonResponse([
          'valid' => FALSE,
          'errors' => $errors,
          'operation_count' => count($operations),
        ]);
      }

      return new JsonResponse([
        'valid' => TRUE,
        'operation_count' => count($operations),
      ]);
    }
    catch (\Exception $e) {
      return new JsonResponse([
        'valid' => FALSE,
        'errors' => [$e->getMessage()],
      ]);
    }
  }

  /**
   * API endpoint for generating operation preview.
   *
   * Route: POST /eb/api/preview
   * Permission: 'import entity architecture'
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The HTTP request containing the content to preview.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with preview data.
   */
  public function preview(Request $request): JsonResponse {
    // Validate CSRF protection.
    $csrf_error = $this->validateAjaxRequest($request);
    if ($csrf_error !== NULL) {
      return $csrf_error;
    }

    $content = $request->getContent();

    if (empty($content)) {
      return new JsonResponse([
        'success' => FALSE,
        'errors' => ['No content provided'],
      ], 400);
    }

    try {
      // First, try to decode as JSON.
      $raw_data = json_decode($content, TRUE);

      if (is_array($raw_data) && $this->operationDataBuilder->isDefinitionFormat($raw_data)) {
        if (!$this->validateJsonDepth($raw_data)) {
          return new JsonResponse([
            'success' => FALSE,
            'errors' => ['Content exceeds maximum nesting depth'],
          ], 400);
        }
        $operation_data = $this->operationDataBuilder->build($raw_data);
      }
      else {
        if (!$this->yamlParser->validateContent($content)) {
          return new JsonResponse([
            'success' => FALSE,
            'errors' => ['Invalid YAML content'],
          ], 400);
        }

        $parsed_data = $this->yamlParser->parse($content);

        if (!$this->validateJsonDepth($parsed_data)) {
          return new JsonResponse([
            'success' => FALSE,
            'errors' => ['Content exceeds maximum nesting depth'],
          ], 400);
        }

        if ($this->operationDataBuilder->isDefinitionFormat($parsed_data)) {
          $operation_data = $this->operationDataBuilder->build($parsed_data);
        }
        else {
          $operation_data = $parsed_data;
        }
      }

      // Build operations.
      $operations = $this->operationBuilder->buildBatch($operation_data);

      if (empty($operations)) {
        return new JsonResponse([
          'success' => TRUE,
          'operation_count' => 0,
          'previews' => [],
          'message' => 'No operations to preview',
        ]);
      }

      // Filter to only previewable operations.
      $previewable_operations = [];
      foreach ($operations as $operation) {
        if ($operation instanceof PreviewableOperationInterface) {
          $previewable_operations[] = $operation;
        }
      }

      // Generate previews.
      $previews = $this->previewGenerator->generateBatchPreview($previewable_operations);

      return new JsonResponse([
        'success' => TRUE,
        'operation_count' => count($operations),
        'previews' => $previews,
      ]);
    }
    catch (\Exception $e) {
      return new JsonResponse([
        'success' => FALSE,
        'errors' => [$e->getMessage()],
      ]);
    }
  }

  /**
   * API endpoint to get bundles for an entity type.
   *
   * Route: GET /eb/api/bundles/{entity_type_id}
   * Permission: 'administer entity builder'
   *
   * @param string $entity_type_id
   *   The entity type ID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with bundle list.
   */
  public function getBundles(string $entity_type_id): JsonResponse {
    try {
      $bundles = [];
      $bundle_data = $this->bundleInfo->getBundleInfo($entity_type_id);

      foreach ($bundle_data as $bundle_id => $data) {
        $bundles[$bundle_id] = [
          'id' => $bundle_id,
          'label' => $data['label'] ?? $bundle_id,
        ];
      }

      return new JsonResponse([
        'success' => TRUE,
        'bundles' => $bundles,
      ]);
    }
    catch (\Exception $e) {
      return new JsonResponse([
        'success' => FALSE,
        'errors' => [$e->getMessage()],
      ], 500);
    }
  }

  /**
   * API endpoint to get entity configuration for a bundle.
   *
   * Route: GET /eb/api/entity-config/{entity_type_id}/{bundle}
   * Permission: 'administer entity builder'
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string $bundle
   *   The bundle name.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with field and display configuration.
   */
  public function getEntityConfig(string $entity_type_id, string $bundle): JsonResponse {
    try {
      // Get field definitions.
      $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
      $fields = [];

      foreach ($field_definitions as $field_name => $definition) {
        // Only include configurable fields (not base fields).
        if (!$definition instanceof FieldConfigInterface) {
          continue;
        }

        $fields[$field_name] = [
          'name' => $field_name,
          'label' => $definition->getLabel(),
          'type' => $definition->getType(),
          'required' => $definition->isRequired(),
        ];
      }

      return new JsonResponse([
        'success' => TRUE,
        'entity_type' => $entity_type_id,
        'bundle' => $bundle,
        'fields' => $fields,
      ]);
    }
    catch (\Exception $e) {
      return new JsonResponse([
        'success' => FALSE,
        'errors' => [$e->getMessage()],
      ], 500);
    }
  }

}
