<?php

declare(strict_types=1);

namespace Drupal\native_observability_otel\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Configuration form for the OpenTelemetry exporter.
 *
 * Displays a prominent top-of-page security warning when the Prometheus
 * bearer token is not configured, because the endpoint is then publicly
 * accessible without any authentication and exposes application metrics
 * (request counts, error rates, route names, latency percentiles) to
 * anyone who can reach the URL.
 */
final class NativeObservabilityOtelSettingsForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'native_observability_otel_settings_form';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames(): array {
    return ['native_observability_otel.settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config('native_observability_otel.settings');

    // Show a top-of-page messenger warning when no bearer token is configured.
    // The messenger block renders above the page title, making it immediately
    // visible without the user needing to scroll to the Prometheus section.
    // This warning persists on every page load until a token is saved.
    if ((string) $config->get('prometheus_bearer_token') === '') {
      $this->messenger()->addWarning(
        $this->t('<strong>Security warning:</strong> The Prometheus metrics endpoint (<code>/native-observability/prometheus</code>) is publicly accessible without authentication. It exposes request counts, error rates, route names, and latency data for your entire application. Set a <strong>Bearer token</strong> in the Prometheus endpoint section below before deploying to production. Generate one with: <code>openssl rand -hex 32</code>')
      );
    }

    $headers = [];
    foreach ((array) $config->get('headers') as $name => $value) {
      $headers[] = $name . ': ' . $value;
    }

    $form['enabled'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable OpenTelemetry exporter'),
      '#default_value' => (bool) $config->get('enabled'),
      '#description' => $this->t('When enabled, completed request traces are exported through the OpenTelemetry exporter.'),
    ];

    $form['endpoint'] = [
      '#type' => 'url',
      '#title' => $this->t('OTLP HTTP endpoint'),
      '#default_value' => (string) $config->get('endpoint'),
      '#description' => $this->t('Example: http://collector:4318/v1/traces'),
      '#required' => FALSE,
    ];

    $form['timeout'] = [
      '#type' => 'number',
      '#title' => $this->t('HTTP timeout'),
      '#default_value' => (float) $config->get('timeout'),
      '#step' => 0.1,
      '#min' => 0.1,
      '#description' => $this->t('Maximum number of seconds allowed for the outbound export request.'),
    ];

    $form['service_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Service name'),
      '#default_value' => (string) $config->get('service_name'),
      '#required' => TRUE,
    ];

    $form['service_namespace'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Service namespace'),
      '#default_value' => (string) $config->get('service_namespace'),
      '#required' => TRUE,
    ];

    $form['service_version'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Service version'),
      '#default_value' => (string) $config->get('service_version'),
      '#required' => TRUE,
    ];

    $form['headers'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Additional HTTP headers'),
      '#default_value' => implode("\n", $headers),
      '#description' => $this->t('Enter one header per line using the format "Header-Name: value".'),
    ];

    $bearerToken = (string) $config->get('prometheus_bearer_token');
    $siteUrl = $this->getRequest()->getSchemeAndHttpHost();
    $snippetToken = $bearerToken !== '' ? $bearerToken : '<your-token>';
    $snippet = "scrape_configs:\n  - job_name: drupal_native_observability\n    bearer_token: {$snippetToken}\n    static_configs:\n      - targets: ['{$siteUrl}']\n    metrics_path: /native-observability/prometheus";

    $form['prometheus'] = [
      '#type' => 'details',
      '#title' => $this->t('Prometheus endpoint'),
      '#open' => TRUE,
    ];

    $form['prometheus']['prometheus_bearer_token'] = [
      '#type' => 'password',
      '#title' => $this->t('Bearer token'),
      '#default_value' => $bearerToken,
      '#description' => $this->t('Token required in the <code>Authorization: Bearer &lt;token&gt;</code> header. Leave empty to disable authentication (development only).'),
    ];

    if ($bearerToken === '') {
      $form['prometheus']['prometheus_bearer_token']['#attributes']['placeholder'] = $this->t('No token set — endpoint is publicly accessible');
      $form['prometheus']['security_warning'] = [
        '#type' => 'item',
        '#markup' => '<div class="messages messages--warning">' . $this->t('<strong>Security warning:</strong> The Prometheus endpoint is publicly accessible without authentication. Set a bearer token before deploying to production. Generate one with: <code>openssl rand -hex 32</code>') . '</div>',
        '#weight' => -10,
      ];
    }

    $form['prometheus']['prometheus_window_seconds'] = [
      '#type' => 'number',
      '#title' => $this->t('Metrics rolling window'),
      '#default_value' => (int) ($config->get('prometheus_window_seconds') ?: 300),
      '#min' => 60,
      '#step' => 1,
      '#description' => $this->t('Duration in seconds of the rolling window used to compute request counts and percentiles. Minimum 60 s, default 300 s (5 minutes).'),
    ];

    $form['prometheus']['prometheus_scrape_snippet'] = [
      '#type' => 'item',
      '#title' => $this->t('Prometheus scrape config'),
      '#markup' => '<pre>' . htmlspecialchars($snippet) . '</pre>',
      '#description' => $this->t('Copy this block into your <code>prometheus.yml</code> scrape configuration.'),
    ];

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    parent::validateForm($form, $form_state);

    $headers = preg_split('/\r\n|\r|\n/', (string) $form_state->getValue('headers')) ?: [];

    foreach ($headers as $header_line) {
      $header_line = trim($header_line);

      if ($header_line === '') {
        continue;
      }

      if (!str_contains($header_line, ':')) {
        $form_state->setErrorByName('headers', $this->t('Each header must use the format "Header-Name: value".'));
        return;
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $headers = [];
    $lines = preg_split('/\r\n|\r|\n/', (string) $form_state->getValue('headers')) ?: [];

    foreach ($lines as $line) {
      $line = trim($line);

      if ($line === '' || !str_contains($line, ':')) {
        continue;
      }

      [$name, $value] = explode(':', $line, 2);
      $headers[trim($name)] = trim($value);
    }

    $config = $this->configFactory()->getEditable('native_observability_otel.settings');

    $newToken = (string) $form_state->getValue('prometheus_bearer_token');
    if ($newToken === '') {
      $newToken = (string) $config->get('prometheus_bearer_token');
    }

    $config
      ->set('enabled', (bool) $form_state->getValue('enabled'))
      ->set('endpoint', (string) $form_state->getValue('endpoint'))
      ->set('timeout', (float) $form_state->getValue('timeout'))
      ->set('service_name', (string) $form_state->getValue('service_name'))
      ->set('service_namespace', (string) $form_state->getValue('service_namespace'))
      ->set('service_version', (string) $form_state->getValue('service_version'))
      ->set('headers', $headers)
      ->set('prometheus_bearer_token', $newToken)
      ->set('prometheus_window_seconds', max(60, (int) $form_state->getValue('prometheus_window_seconds')))
      ->save();

    parent::submitForm($form, $form_state);
  }

}
