<?php

namespace Drupal\keycloak_user_provisioning\Form;

use Drupal\Core\Render\Markup;
use GuzzleHttp\Exception\RequestException;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\keycloak_user_provisioning\Helper\MoKeycloakHelper;
use Drupal\user_provisioning\Helpers\moUserProvisioningLogger;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Provides a form for reviewing Keycloak configuration.
 */
class MoReviewKeycloakConfiguration extends FormBase {
  use StringTranslationTrait;
  /**
   * Base URL of the site.
   *
   * @var string
   */
  private $baseUrl;

  /**
   * ImmutableConfig property.
   *
   * @var Drupal\Core\Config\ImmutableConfig
   */
  private ImmutableConfig $config;

  /**
   * Editable config property.
   *
   * @var Drupal\Core\Config\Config
   */
  protected $editableConfig;

  /**
   * Logger property.
   *
   * @var Drupal\user_provisioning\Helpers\moUserProvisioningLogger
   */
  private moUserProvisioningLogger $moLogger;

  /**
   * The httpclient property.
   *
   * @var \GuzzleHttp\Client
   */
  private Client $httpClient;

  /**
   * Config factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $configFactoryService;

  /**
   * Module extension list.
   *
   * @var \Drupal\Core\Extension\ModuleExtensionList
   */
  protected ModuleExtensionList $moduleExtensionList;

  /**
   * Current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  /**
   * Messenger property.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * The mo keycloak helper.
   *
   * @var \Drupal\keycloak_user_provisioning\Helper\MoKeycloakHelper
   */
  private MoKeycloakHelper $moKeycloakHelper;

  /**
   * Constructs a new MoReviewKeycloakConfiguration object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   * @param \GuzzleHttp\Client $http_client
   *   The HTTP client.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\Extension\ModuleExtensionList $module_extension_list
   *   The module extension list.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory.
   * @param \Drupal\keycloak_user_provisioning\Helper\MoKeycloakHelper $moKeycloakHelper
   *   The Keycloak helper service.
   */
  public function __construct(
    ConfigFactoryInterface $config_factory,
    MessengerInterface $messenger,
    Client $http_client,
    RequestStack $request_stack,
    ModuleExtensionList $module_extension_list,
    AccountProxyInterface $current_user,
    LoggerChannelFactoryInterface $logger_factory,
    MoKeycloakHelper $moKeycloakHelper,
  ) {
    $base_url = $request_stack->getCurrentRequest()->getSchemeAndHttpHost();
    $this->baseUrl = $base_url;
    $this->configFactoryService = $config_factory;
    $this->config = $config_factory->get('keycloak_user_provisioning.settings');
    $this->editableConfig = $config_factory->getEditable('keycloak_user_provisioning.settings');
    $this->messenger = $messenger;
    $this->moLogger = new moUserProvisioningLogger();
    $this->httpClient = $http_client;
    $this->requestStack = $request_stack;
    $this->moduleExtensionList = $module_extension_list;
    $this->currentUser = $current_user;
    $this->loggerFactory = $logger_factory;
    $this->moKeycloakHelper = $moKeycloakHelper;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('messenger'),
      $container->get('http_client'),
      $container->get('request_stack'),
      $container->get('extension.list.module'),
      $container->get('current_user'),
      $container->get('logger.factory'),
      $container->get('keycloak_user_provisioning.helper')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'review_keycloak_configuration_popup';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['#prefix'] = '<div id="review_keycloak_configuration">';
    $form['#suffix'] = '</div>';
    $form['status_messages'] = [
      '#type' => 'status_messages',
      '#weight' => -10,
    ];

    $form['markup_library'] = [
      '#attached' => [
        'library' => [
          "keycloak_user_provisioning/keycloak_user_provisioning.admin",
        ],
      ],
    ];

    $tableVar = new MoKeycloakOverview(
      $this->configFactoryService,
      $this->messenger,
      $this->requestStack,
      $this->moduleExtensionList,
      $this->currentUser,
      $this->moKeycloakHelper
    );
    $tableVar->keycloakConfiguration($form, $form_state, 'summary');

    if (
          !empty($this->config->get('keycloak_user_provisioning_base_url')) &&
          !empty($this->config->get('keycloak_user_provisioning_client_id')) &&
          !empty($this->config->get('keycloak_user_provisioning_client_secret')) &&
          !empty($this->config->get('keycloak_user_provisioning_realm'))
      ) {
      $this->testAttributesFromKeycloak($form, $form_state);
    }

    $form['actions'] = ['#type' => 'actions'];
    $form['actions']['send'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save and Test Configuration'),
      '#attributes' => [
        'class' => [
          'use-ajax',
          'button--primary',
        ],
      ],
      '#ajax' => [
        'callback' => [$this, 'saveReviewKeycloakConfig'],
        'event' => 'click',
      ],
    ];
    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';

    return $form;
  }

  /**
   * Tests and displays attributes from Keycloak.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  private function testAttributesFromKeycloak(array &$form, FormStateInterface $form_state) {
    $keycloak_test_data = '';
    if (($this->config->get('mo_keycloak_attr_list_from_server')) != '') {
      $keycloak_test_data = Json::decode($this->config->get('mo_keycloak_attr_list_from_server'));
    }

    $form['mo_keycloak_summary_configuration']['keycloak_configuration_display_test_data'] = [
      '#type' => 'details',
      '#title' => $this->t('Test Configuration Result'),
      '#open' => isset($keycloak_test_data["error"]) ,
      '#prefix' => '<div id="test_config_result">',
    ];

    $status_message = '';

    if ($keycloak_test_data != '') {
      if (isset($keycloak_test_data["error"])) {
        $status_message = Markup::create('
      <div style="font-family:Calibre,serif;padding:0 3%;">
        <div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;">
            ERROR
        </div>
        <div style="color: #a94442;font-size:14pt; margin-bottom:20px;">
    ');
      }
      else {
        $status_message = Markup::create('
      <div style="font-family:Calibre,serif;padding:0 3%;">
        <div style="width:95%;color: #3c763d;background-color: #dff0d8;padding: 2%;margin-bottom: 20px;text-align: center;border: 1px solid #AEDB9A;font-size: 18pt;">
            SUCCESS
        </div>
      </div>
    ');
      }
    }
    else {
      $status_message = Markup::create('
    <div style="font-family:Calibre,serif;padding:0 3%;">
      <div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;">
          ERROR
      </div>
      <div style="color: #a94442;font-size:14pt; margin-bottom:20px;">
    </div>
  ');
    }

    $form['mo_keycloak_summary_configuration']['keycloak_configuration_display_test_data']['header_message'] = [
      '#markup' => $status_message,
    ];

    $form['mo_keycloak_summary_configuration']['keycloak_configuration_display_test_data']['keycloak_configuration_show_test_data'] = [
      '#type' => 'table',
      '#responsive' => TRUE,
      '#attributes' => ['style' => 'border-collapse: separate;', 'class' => ['mo_keycloak_test_data']],
    ];

    if ($keycloak_test_data != NULL) {
      foreach ($keycloak_test_data as $key => $value) {
        $test_result = new MoKeycloakOverview(
          $this->configFactoryService,
          $this->messenger,
          $this->requestStack,
          $this->moduleExtensionList,
          $this->currentUser,
          $this->moKeycloakHelper
        );
        $row = $test_result->keycloakTestConfigurationData($key, $value);
        $form['mo_keycloak_summary_configuration']['keycloak_configuration_display_test_data']['keycloak_configuration_show_test_data'][$key] = $row;
      }
    }
  }

  /**
   * Saves Keycloak configuration and performs test configuration.
   *
   * @return Symfony\Component\HttpFoundation\Response
   *   Returns response object
   *
   * @throws \Exception
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function saveReviewKeycloakConfig(array $form, FormStateInterface $form_state) {

    $form_values = $form_state->getValues();

    $data = [
      'keycloak_user_provisioning_summary_base_url' => 'keycloak_user_provisioning_base_url',
      'keycloak_user_provisioning_summary_client_id' => 'keycloak_user_provisioning_client_id',
      'keycloak_user_provisioning_summary_client_secret' => 'keycloak_user_provisioning_client_secret',
      'keycloak_user_provisioning_summary_realm' => 'keycloak_user_provisioning_realm',
    ];

    foreach ($data as $key => $value) {
      $this->editableConfig->set($value, $form_values[$key])->save();
    }

    $this->configFactoryService->getEditable('user_provisioning.settings')->set('mo_user_provisioning_configured_application', 'keycloak')
      ->save();

    $this->fetchAttributesFromKeycloak();

    $keycloak_test_data = Json::decode($this->config->get('mo_keycloak_attr_list_from_server'));

    if ($keycloak_test_data != '') {
      if (isset($keycloak_test_data["error"])) {
        $this->messenger()->addError($this->t('An error occurred while performing test configuration. Please refer to <a href="#test_config_result">Test Configuration Result</a> for more information.'));
      }
      else {
        $this->messenger()->addStatus($this->t('Test Configuration successful. You can check the complete list of attributes received from Keycloak by clicking on the link <a href="#test_config_result">HERE</a>.'));
      }
    }
    else {
      $this->messenger()->addError('Something went wrong. Please check your configurations.');
    }

    $response = new AjaxResponse();
    $this->testAttributesFromKeycloak($form, $form_state);

    $response->addCommand(new ReplaceCommand('#review_keycloak_configuration', $form));

    return $response;
  }

  /**
   * Fetches attributes from Keycloak.
   *
   * @throws \Exception
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function fetchAttributesFromKeycloak() {
    $response = $this->getUserFromKeycloakInPopup();
    $content = json_decode($response);
    if ($content != NULL) {
      $user_details = $this->moKeycloakHelper->moKeycloakArrayFlattenAttributes($content);
      $user_details_encoded = Json::encode($user_details);
      $this->editableConfig
        ->set('mo_keycloak_attr_list_from_server', $user_details_encoded)
        ->save();
    }
    else {
      $this->editableConfig
        ->set('mo_keycloak_attr_list_from_server', '')
        ->save();
    }
  }

  /**
   * Gets user data from Keycloak in popup.
   *
   * @return string
   *   Encoded user details.
   *
   * @throws \Exception
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function getUserFromKeycloakInPopup() {
    $access_token = $this->getKeyCloakAccessToken();

    if (empty($access_token)) {
      $this->messenger()->addMessage(
            $this->t('Unable to fetch attributes. Failed to obtain access token from Keycloak. Please verify your Keycloak configuration settings and try again.'),
            MessengerInterface::TYPE_ERROR
        );
    }

    $token_data = json_decode($access_token, TRUE);
    // Check if access_token contains an error JSON or a valid token string.
    if (is_array($token_data) && isset($token_data['error'])) {
      // Access token response contains an error.
      $content = $token_data;
    }
    else {
      // Access token is valid; fetch users from Keycloak.
      $response = $this->getUsersFromKeycloak($access_token);

      // Decode response safely (if it’s JSON)
      $decoded_response = json_decode($response, TRUE);
      $content = is_array($decoded_response) ? $decoded_response : [];
    }

    $user_details = $this->moKeycloakHelper->moKeycloakArrayFlattenAttributes(is_array($content) && !isset($content['error']) ? $content[0] : $content);

    $user_details_encoded = Json::encode($user_details);

    $this->editableConfig
      ->set('mo_keycloak_attr_list_from_server', $user_details_encoded)
      ->save();

    return $user_details_encoded;
  }

  /**
   * Gets users from Keycloak using access token.
   *
   * @param string $access_token
   *   The access token for authentication.
   *
   * @return string
   *   The response content.
   */
  public function getUsersFromKeycloak($access_token) {
    $url = $this->getUsersEndPointUrl();
    $options = [
      'timeout' => 30,
      'headers' => [
        'Authorization' => 'Bearer ' . $access_token,
        'Content-Type' => 'application/json',
      ],
    ];

    try {
      $response = $this->httpClient->request('GET', $url, $options);
    }
    catch (GuzzleException $exception) {
      if ($exception->getCode() == 0) {
        $error_msg = $exception->getMessage();
        $error_code = [
          "%error" => $error_msg,
          "%Description" => "Please Configure the fields correctly.",
        ];
        $this->getLogger('user_provisioning')->notice('Error: %error Cause: %Description', $error_code);
        $this->messenger()->addError($this->t('@error', ['@error' => $error_msg]));
        $this->editableConfig
          ->set('mo_keycloak_attr_list_from_server', '')
          ->save();
        $response = new RedirectResponse($this->baseUrl . '/admin/config/people/keycloak_user_provisioning/overview?tab=drupal-to-keycloak-configuration');
        $response->send();
        exit();
      }
      else {
        $this->editableConfig
          ->set('mo_keycloak_attr_list_from_server', '')
          ->save();
        $error = [
          '%error' => $exception->getResponse()->getBody()->getContents(),
        ];
        $this->getLogger('user_provisioning')->notice('Error:  %error', $error);
        foreach ($error as $error_value) {
          return $error_value;
        }
      }
    }

    $content = $response->getBody()->getContents();
    return $content;
  }

  /**
   * Gets the users endpoint URL from Keycloak configuration.
   *
   * @return string
   *   The users endpoint URL.
   */
  public function getUsersEndPointUrl() {
    $keycloak_realm = $this->config->get('keycloak_user_provisioning_realm');
    $keycloak_domain = $this->config->get('keycloak_user_provisioning_base_url');
    return rtrim($keycloak_domain, '/') . '/admin/realms/' . $keycloak_realm . '/users';
  }

  /**
   * Gets the Keycloak access token using client credentials.
   *
   * @return string|null
   *   The access token or error message, or NULL on failure.
   */
  public function getKeyCloakAccessToken() {
    $keycloak_domain = $this->config->get('keycloak_user_provisioning_base_url');
    $keycloak_client_id = $this->config->get('keycloak_user_provisioning_client_id');
    $keycloak_client_secret = $this->config->get('keycloak_user_provisioning_client_secret');
    $keycloak_user_provisioning_realm = $this->config->get('keycloak_user_provisioning_realm');

    $token_url = rtrim($keycloak_domain, '/') .
            '/realms/' . $keycloak_user_provisioning_realm .
            '/protocol/openid-connect/token';

    try {
      $response = $this->httpClient->request('POST', $token_url, [
        'timeout' => 30,
        'headers' => [
          'Content-Type' => 'application/x-www-form-urlencoded',
        ],
        'form_params' => [
          'grant_type' => 'client_credentials',
          'client_id' => $keycloak_client_id,
          'client_secret' => $keycloak_client_secret,
        ],
      ]);

      $status_code = $response->getStatusCode();
      if ($status_code !== 200) {
        $this->messenger()->addMessage("Failed to fetch access token. HTTP status: {$status_code}", MessengerInterface::TYPE_ERROR);
        return NULL;
      }

      $body = (string) $response->getBody();
      $token = json_decode($body, TRUE);

      if (json_last_error() !== JSON_ERROR_NONE) {
        $this->messenger()->addMessage('Invalid JSON response from Keycloak: ' . json_last_error_msg(), MessengerInterface::TYPE_ERROR);
        return NULL;
      }

      if (empty($token['access_token'])) {
        $this->messenger()->addMessage('Access token not found in Keycloak response.', MessengerInterface::TYPE_ERROR);
        return NULL;
      }

      return $token['access_token'];

    }
    catch (RequestException $e) {
      $this->editableConfig
        ->set('mo_keycloak_attr_list_from_server', '')
        ->save();

      $message = $e->getMessage();

      if (preg_match('/(\{.*\})/s', $message, $matches)) {
        // Found a JSON error in the message.
        $error_message = $matches[1];
        json_decode($error_message, TRUE);

        if (json_last_error() !== JSON_ERROR_NONE) {
          $error_data = [
            'error' => 'unexpected_response',
            'user_message' => 'We received an unexpected response from the server. Please try again later.',
          ];
        }
      }
      else {
        // No JSON found — detect common error types.
        if (str_contains($message, '401')) {
          $error_message = '{"error":"Authentication failed. Please verify your Client ID and Client Secret."}';
        }
        elseif (str_contains($message, '404')) {
          $error_message = '{"error":"Requested resource not found. Please check your Keycloak Domain or Realm settings."}';
        }
        else {
          $error_message = '{"error":"Something went wrong while connecting to the server. Please try again later."}';
        }
      }

      return $error_message;
    }
    catch (\Exception $e) {
      $this->messenger()->addMessage('Error while fetching Keycloak access token: ' . $e->getMessage(), MessengerInterface::TYPE_ERROR);
      return NULL;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Handle form submission.
  }

}
