<?php

declare(strict_types=1);

namespace Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Http\ClientFactory;
use Drupal\flowdrop\Attribute\FlowDropNodeProcessor;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop\DTO\ValidationResult;
use Drupal\flowdrop\Plugin\FlowDropNodeProcessor\AbstractFlowDropNodeProcessor;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Executor for HTTP Request nodes.
 */
#[FlowDropNodeProcessor(
  id: "http_request",
  label: new \Drupal\Core\StringTranslation\TranslatableMarkup("HTTP Request"),
  description: "HTTP request operations",
  version: "1.0.0"
)]
class HttpRequest extends AbstractFlowDropNodeProcessor {

  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\Client
   */
  protected Client $httpClient;

  /**
   * Constructs an HttpRequest object.
   *
   * @param array<string, mixed> $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin ID.
   * @param mixed $plugin_definition
   *   The plugin definition.
   * @param \Drupal\Core\Http\ClientFactory $clientFactory
   *   The HTTP client factory.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    private readonly ClientFactory $clientFactory,
  ) {
    $this->httpClient = $this->clientFactory->fromOptions([
      "timeout" => 30,
      "connect_timeout" => 10,
    ]);
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get("http_client_factory")
    );
  }

  /**
   * {@inheritdoc}
   */
  public function validateParams(array $params): ValidationResult {
    // HTTP Request nodes don't require specific inputs.
    return ValidationResult::success();
  }

  /**
   * {@inheritdoc}
   */
  public function process(ParameterBagInterface $params): array {
    $url = $params->get("url") ?? $params->get("url", "");
    $method = strtoupper($params->get("method") ?? $params->get("method", "GET"));

    // Merge config headers with runtime headers.
    $configHeaders = $params->get("headers", []);
    $runtimeHeaders = $params->get("headers", []);
    $headers = array_merge($configHeaders, $runtimeHeaders);

    $body = $params->get("body") ?? $params->get("body", "");
    $timeout = $params->get("timeout", 30);

    if (empty($url)) {
      throw new \Exception("No URL provided for HTTP request");
    }

    // Validate URL.
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
      throw new \Exception("Invalid URL provided: " . $url);
    }

    try {
      // Prepare request options.
      $options = [
        "timeout" => $timeout,
        "headers" => $headers,
      ];

      // Add body for POST/PUT requests.
      if (in_array($method, ["POST", "PUT", "PATCH"]) && !empty($body)) {
        $options["body"] = $body;
      }

      // Make the request.
      $response = $this->httpClient->request($method, $url, $options);

      // Get response data.
      $statusCode = $response->getStatusCode();
      $responseHeaders = $response->getHeaders();
      $responseBody = $response->getBody()->getContents();

      // Try to parse JSON response.
      $jsonData = NULL;
      if (str_contains($responseHeaders["Content-Type"][0] ?? "", "application/json")) {
        $jsonData = JSON::decode($responseBody, TRUE);
      }

      return [
        "status_code" => $statusCode,
        "headers" => $responseHeaders,
        "body" => $responseBody,
        "json" => $jsonData,
        "url" => $url,
        "method" => $method,
        "request_time" => microtime(TRUE),
      ];
    }
    catch (RequestException $e) {
      throw new \Exception("HTTP request failed: " . $e->getMessage());
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "url" => [
          "type" => "string",
          "title" => "URL",
          "description" => "The URL to request",
          "default" => "",
        ],
        "method" => [
          "type" => "string",
          "title" => "HTTP Method",
          "description" => "The HTTP method to use",
          "default" => "GET",
          "enum" => ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
        ],
        "headers" => [
          "type" => "object",
          "format" => "json",
          "title" => "Headers",
          "description" => "HTTP headers to send",
          "default" => [],
        ],
        "body" => [
          "type" => "string",
          "format" => "multiline",
          "title" => "Request Body",
          "description" => "Request body for POST/PUT requests",
          "default" => "",
        ],
        "timeout" => [
          "type" => "integer",
          "title" => "Timeout",
          "description" => "Request timeout in seconds",
          "default" => 30,
          "minimum" => 1,
          "maximum" => 300,
        ],
        "follow_redirects" => [
          "type" => "boolean",
          "title" => "Follow Redirects",
          "description" => "Whether to follow HTTP redirects",
          "default" => TRUE,
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getOutputSchema(): array {
    return [
      "type" => "object",
      "properties" => [
        "status_code" => [
          "type" => "integer",
          "description" => "HTTP response status code",
        ],
        "headers" => [
          "type" => "array",
          "description" => "HTTP response headers",
        ],
        "body" => [
          "type" => "string",
          "description" => "HTTP response body",
        ],
        "json" => [
          "type" => "array",
          "description" => "Parsed JSON response (if applicable)",
        ],
        "url" => [
          "type" => "string",
          "description" => "The URL that was requested",
        ],
        "method" => [
          "type" => "string",
          "description" => "The HTTP method used",
        ],
        "request_time" => [
          "type" => "float",
          "description" => "Timestamp when request was made",
        ],
      ],
    ];
  }

  /**
   * Test connection to a URL.
   *
   * @param string $url
   *   The URL to test.
   * @param int $timeout
   *   Connection timeout.
   *
   * @return array{reachable: bool, status_code?: int, response_time?: float, url: string, error?: string}
   *   Test results.
   */
  protected function testConnection(string $url, int $timeout): array {
    try {
      $startTime = microtime(TRUE);

      // Use HEAD request for faster connection test.
      $response = $this->httpClient->request("HEAD", $url, [
        "timeout" => $timeout,
        "connect_timeout" => $timeout,
      ]);

      $responseTime = microtime(TRUE) - $startTime;

      return [
        "reachable" => TRUE,
        "status_code" => $response->getStatusCode(),
        "response_time" => $responseTime,
        "url" => $url,
      ];
    }
    catch (\Exception $e) {
      return [
        "reachable" => FALSE,
        "error" => $e->getMessage(),
        "url" => $url,
      ];
    }
  }

}
