<?php

namespace Drupal\azure_ad\Form;

use Drupal\azure_ad\Helper\moAzureADHelper;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\GeneratedUrl;
use Drupal\Core\Link;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\user_provisioning\moUserProvisioningConstants;
use Drupal\user_provisioning\Helpers\moUserProvisioningLogger;
use Drupal\user_provisioning\moUserProvisioningOperationsHandler;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;

/**
 * Drupal to Azure Sync Form.
 */
class MoDrupalToAzureSync extends FormBase {
  /**
   * Immutable Azure AD module configuration.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  private ImmutableConfig $config;

  /**
   * User provisioning logger.
   *
   * @var \Drupal\user_provisioning\Helpers\moUserProvisioningLogger
   */
  private moUserProvisioningLogger $moLogger;

  /**
   * Entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  private EntityFieldManagerInterface $entityFieldManager;

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private EntityTypeManagerInterface $entityTypeManager;

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

  /**
   * Base URL.
   *
   * @var string|GeneratedUrl
   */
  private string|GeneratedUrl $baseUrl;

  /**
   * Constructor for MoDrupalToAzureSync.
   */
  public function __construct(
    ConfigFactoryInterface $config_factory,
    MessengerInterface $messenger,
    EntityFieldManagerInterface $entity_field_manager,
    EntityTypeManagerInterface $entity_type_manager,
    AccountProxyInterface $current_user,
  ) {
    $this->setConfigFactory($config_factory);
    $this->setMessenger($messenger);
    $this->config = $config_factory->get('azure_ad.settings');
    $this->entityFieldManager = $entity_field_manager;
    $this->entityTypeManager = $entity_type_manager;
    $this->currentUser = $current_user;
    $this->moLogger = new moUserProvisioningLogger();
    $this->baseUrl = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
  }

  /**
   * {@inheritDoc}
   */
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('config.factory'),
      $container->get('messenger'),
      $container->get('entity_field.manager'),
      $container->get('entity_type.manager'),
      $container->get('current_user')
    );
  }

  /**
   * Get the form ID.
   */
  public function getFormId() {
    return "mo_drupal_to_azure";
  }

  /**
   * Build the form.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['mo_azure_ad_overview_add_css'] = [
      '#attached' => [
        'library' => [
          'azure_ad/azure_ad.admin',
          'core/drupal.dialog.ajax',
        ],
      ],
    ];

    $test_config_result = $this->config->get('test_azure_config_result');

    if ($test_config_result == NULL) {
      $test_config_result = FALSE;
    }
    elseif ($test_config_result == TRUE) {
      $url = Url::fromRoute('azure_ad.reviewconfig');

      $url->setOptions([
        'attributes' => [
          'class' => ['use-ajax'],
          'data-dialog-type' => 'modal',
          'data-dialog-options' => json_encode([
            'width' => '60%',
            'title' => 'Review Azure Configuration',
          ]),
        ],
      ]);

      $link = Link::fromTextAndUrl(
            $this->t('Review Azure Configurations'),
            $url
        )->toString();

      $message = $this->t(
            'An error occurred while performing the test configuration. Please click on the @link button to perform the Test configuration again.',
            ['@link' => $link]
        );

      $this->messenger->addError(Markup::create($message));
    }

    $form['mo_azure_ad_manual_provisioning_back_to_overview'] = [
      '#type' => 'submit',
      '#value' => $this->t('&#11164; &nbsp;Back to Provisioning types'),
      '#button_type' => 'danger',
      '#submit' => ['::moAzureProvBackStep4Button'],
      '#limit_validation_errors' => [],
    ];

    $form['review_config'] = [
      '#type' => 'link',
      '#title' => $this->t('Review Azure Configurations'),
      '#url' => Url::fromRoute('azure_ad.reviewconfig'),
      '#attributes' => [
        'class' => [
          'button',
          'button--primary',
          'use-ajax',
        ],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => json_encode([
          'width' => '60%',
          'title' => 'Azure Configuration',
        ]),
        'style' => 'float:right;',
      ],
    ];

    $form['redirect_to_mapping'] = [
      '#type' => 'submit',
      '#value' => $this->t('Attribute Mapping'),
      '#submit' => ['::redirectToMappingTab'],
      '#attributes' => ['style' => 'float: right;'],
      '#limit_validation_errors' => [],
    ];

    $status = $this->config->get('azure_ad_provisioning_step');

    $this->azureAdManualProvisioning($form, $form_state);
    $this->azureAdAutoProvisioning($form, $form_state);
    $this->azureAdSchedulerProvisioning($form, $form_state);

    $form['submit_button'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save Configuration'),
      '#button_type' => 'primary',
      '#disabled' => $test_config_result,
    ];

    $azure_ad_helper = new moAzureADHelper();
    $azure_ad_helper->moAzureShowCustomerSupportIcon($form, $form_state);

    return $form;
  }

  /**
   * Submit the form.
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {

    $form_values = $form_state->getValues();

    $manually_create_user = $form_values['create_user_azure_ad_manual'];
    $auto_create_user = $form_values['create_user_azure_ad_automatic'];

    $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_manual_provisioning_checkbox', $manually_create_user)->save();
    $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_automatic_provisioning_checkbox', $auto_create_user)->save();
    $this->messenger->addStatus($this->t('Configurations saved successfully.'));
  }

  /**
   * Redirect to the mapping tab.
   */
  public function redirectToMappingTab(array &$form, FormStateInterface $form_state) {
    $response = new RedirectResponse(Url::fromRoute('azure_ad.mapping')->toString());
    $response->send();
    return new Response();
  }

  /**
   * Azure AD Automatic Provisioning.
   */
  private function azureAdAutoProvisioning(array &$form, FormStateInterface $form_state) {

    $form['mo_azure_ad_auto_provisioning_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Automatic Provisioning Configurations'),
    ];

    $this->moProvisioningOperations($form, 'automatic', 'mo_azure_ad_auto_provisioning_fieldset');
  }

  /**
   * Azure AD Scheduler Provisioning.
   */
  private function azureAdSchedulerProvisioning(array &$form, FormStateInterface $form_state) {
    $url = Url::fromRoute('azure_ad.azure_upgrade_plans')->toString();
    $mo_premium_tag = '<a href="' . $url . '" class="premium-badge">' . $this->t('PREMIUM') . '</a>';
    $form['mo_azure_ad_scheduler_provisioning_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Scheduler Based Provisioning Configuration&nbsp;') . $mo_premium_tag,
    ];

    $this->moProvisioningOperations($form, 'scheduler', 'mo_azure_ad_scheduler_provisioning_fieldset');
  }

  /**
   * Azure AD Manual Provisioning.
   */
  private function azureAdManualProvisioning(array &$form, FormStateInterface $form_state) {

    $test_config_result = $this->config->get('test_azure_config_result');

    if ($test_config_result == NULL) {
      $test_config_result = FALSE;
    }

    $form['mo_azure_ad_manual_provisioning_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Manual Provisioning Configurations'),
    ];
    $this->moProvisioningOperations($form, 'manual', 'mo_azure_ad_manual_provisioning_fieldset');
    $form['mo_azure_ad_manual_provisioning_fieldset']['manual_provisioning_choose_operations']['create_user_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Sync Users') . '<hr>',
      '#states' => [
        'visible' => [
          ':input[name="create_user_azure_ad_manual"]' => ['checked' => TRUE],
        ],
      ],
      '#prefix' => '<div class="container-inline">',
      '#suffix' => '</div>',
    ];

    $form['mo_azure_ad_manual_provisioning_fieldset']['manual_provisioning_choose_operations']['create_user_fieldset']['mo_azure_drupal_username'] = [
      '#type' => 'entity_autocomplete',
      '#target_type' => 'user',
      '#attributes' => ['placeholder' => $this->t('Search Drupal username of user to sync')],
      '#prefix' => '<p class="mo_azure_highlight_background"><strong>' . $this->t('Note:') . '</strong> ' . $this->t('Search the username of user in Drupal to sync (create) it to the Active Directory.') . '</p>',
      '#disabled' => $test_config_result,
    ];

    $form['mo_azure_ad_manual_provisioning_fieldset']['manual_provisioning_choose_operations']['create_user_fieldset']['mo_azure_sync_button'] = [
      '#type' => 'submit',
      '#value' => $this->t('Sync'),
      '#disabled' => $test_config_result,
      '#button_type' => 'primary',
      '#attributes' => ['class' => ['mo_azure_sync_button']],
      '#submit' => ['::moAzureManualSync'],
    ];

    $form['mo_azure_ad_manual_provisioning_fieldset']['manual_provisioning_choose_operations']['create_user_fieldset']['mo_azure_sync_all_button'] = [
      '#type' => 'submit',
      '#value' => $this->t('Sync All Users'),
      '#button_type' => 'primary',
      '#attributes' => ['class' => ['mo_azure_sync_all_button']],
      '#disabled' => TRUE,
    ];
  }

  /**
   * Provisioning operations.
   */
  public function moProvisioningOperations(&$form, $provision_type, $fieldset = '') {
    if ($provision_type == 'manual') {
      $type = $this->t('Manually');
    }
    elseif ($provision_type == 'automatic') {
      $type = $this->t('Automatically');
    }
    else {
      $type = $this->t('On Cron Run');
    }

    $test_config_result = $this->config->get('test_azure_config_result');

    if ($test_config_result == NULL) {
      $test_config_result = FALSE;
    }

    $form[$fieldset]['read_user_azure_ad'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Read user'),
      '#default_value' => $provision_type != 'scheduler',
      '#disabled' => TRUE,
      '#prefix' => '<div class="azure_ad_checkboxes">',
    ];

    $form[$fieldset]['create_user_azure_ad_' . $provision_type] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Create user'),
      '#default_value' => $provision_type == 'scheduler' ? FALSE : $this->config->get('azure_ad_' . $provision_type . '_provisioning_checkbox'),
      '#disabled' => $provision_type == 'scheduler' || $test_config_result,
    ];
    $url = Url::fromRoute('azure_ad.azure_upgrade_plans')->toString();
    $mo_premium_tag = '<a href="' . $url . '" class="premium-badge">' . $this->t('PREMIUM') . '</a>';
    $form[$fieldset]['update_user_azure_ad'] = [
      '#type' => 'checkbox',
      '#title' => $provision_type == 'scheduler' ? $this->t('Update user') : $this->t('Update user&nbsp;') . $mo_premium_tag,
      '#disabled' => TRUE,
    ];

    $form[$fieldset]['deactivate_user_azure_ad'] = [
      '#type' => 'checkbox',
      '#title' => $provision_type == 'scheduler' ? $this->t('Deactivate user') : $this->t('Deactivate user&nbsp;') . $mo_premium_tag,
      '#disabled' => TRUE,
    ];

    $form[$fieldset]['delete_user_azure_ad'] = [
      '#type' => 'checkbox',
      '#title' => $provision_type == 'scheduler' ? $this->t('Delete user') : $this->t('Delete user&nbsp;') . $mo_premium_tag,
      '#disabled' => TRUE,
      '#suffix' => '</div>',
    ];

  }

  /**
   * Attribute mapping.
   */
  public function attributeMapping(array &$form, FormStateInterface $form_state) {

    $form['mo_azure_attribute_mapping_details'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Mapping'),
    ];

    $form['mo_azure_attribute_mapping_details']['azure_ad_attribute_mapping'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Basic Attribute Mapping') . '<hr>',
    ];

    $form['mo_azure_attribute_mapping_details']['azure_ad_attribute_mapping']['azure_ad_attribute_mapping_table'] = [
      '#type' => 'table',
      '#responsive' => TRUE,
      '#attributes' => ['class' => ['attribute_mapping_table']],
    ];

    $row = $this->moAzureAdAttributeMapTable();
    $form['mo_azure_attribute_mapping_details']['azure_ad_attribute_mapping']['azure_ad_attribute_mapping_table']['attributes'] = $row;
    $url = Url::fromRoute('azure_ad.azure_upgrade_plans')->toString();
    $mo_premium_tag = '<a href="' . $url . '" class="premium-badge">' . $this->t('PREMIUM') . '</a>';
    $form['mo_azure_attribute_mapping_details']['azure_ad_custom_attribute_mapping'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Custom Attribute Mapping&nbsp;') . $mo_premium_tag,
    ];

    $form['mo_azure_attribute_mapping_details']['azure_ad_custom_attribute_mapping']['attribute_mapping_info'] = [
      '#markup' => '<div class="mo_azure_highlight_background">This feature allows you to map the user attributes from your Drupal to Azure AD.</div>',
    ];

    $custom_fields = [];
    $usr = $this->entityTypeManager->getStorage('user')->load($this->currentUser->id());
    $usrVal = $usr ? $usr->toArray() : [];
    foreach ($usrVal as $key => $value) {
      $custom_fields[$key] = $key;
    }

    $form['mo_azure_attribute_mapping_details']['azure_ad_custom_attribute_mapping']['azure_ad_custom_attribute_mapping_table'] = [
      '#type' => 'table',
      '#responsive' => TRUE,
      '#attributes' => ['class' => ['custom_attribute_mapping_table']],
    ];

    $row = $this->moAzureAdCustomAttributeMapTable($custom_fields);
    $form['mo_azure_attribute_mapping_details']['azure_ad_custom_attribute_mapping']['azure_ad_custom_attribute_mapping_table']['custom_mapping'] = $row;
  }

  /**
   * Attribute mapping table.
   */
  private function moAzureAdAttributeMapTable() {

    $default_user_fields = $this->entityFieldManager->getFieldDefinitions('user', NULL);
    foreach ($default_user_fields as $key => $value) {
      $default_user_fields[$key] = $key;
    }

    $email_attr = $this->config->get('azure_ad_mapping_mail_attr');
    $username_attr = $this->config->get('azure_ad_mapping_username_attr');

    if (!isset($email_attr)) {
      $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_mapping_mail_attr', 'mail')->save();
    }

    if (!isset($username_attr)) {
      $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_mapping_username_attr', 'name')->save();
    }

    $row['username_attribute'] = [
      '#type' => 'select',
      '#title' => $this->t('Username Attribute'),
      '#options' => $default_user_fields,
      '#default_value' => $this->config->get('azure_ad_mapping_username_attr'),
    ];

    $row['email_attribute'] = [
      '#type' => 'select',
      '#title' => $this->t('Email Attribute'),
      '#options' => $default_user_fields,
      '#default_value' => $this->config->get('azure_ad_mapping_mail_attr'),
    ];

    return $row;
  }

  /**
   * Custom attribute mapping table.
   */
  private function moAzureAdCustomAttributeMapTable($custom_fields) {
    $row['azure_ad_drupal_attr_name'] = [
      '#title' => $this->t('Drupal Attribute Name'),
      '#type' => 'select',
      '#options' => $custom_fields,
      '#disabled' => TRUE,
    ];

    $row['azure_ad_attribute_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Azure AD Attribute Name'),
      '#disabled' => TRUE,
    ];

    $row['azure_ad_add_button'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#disabled' => TRUE,
      '#value' => '+',
    ];

    $row['azure_ad_sub_button'] = [
      '#type' => 'submit',
      '#button_type' => 'danger',
      '#disabled' => TRUE,
      '#value' => '-',
    ];

    return $row;
  }

  /**
   * Save settings for Azure AD Manual Provisioning.
   */
  public function saveSettingsAzureAdManualProv(array &$form, FormStateInterface $form_state) {
    $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_manual_provisioning_checkbox', $form_state->getValues()['azure_ad_manual_provisioning_operations']['operations']['create_user_azure_ad_manual'])->save();
    $this->messenger->addStatus($this->t('Configurations saved successfully.'));
  }

  /**
   * Save settings for Azure AD Automatic Provisioning.
   */
  public function saveSettingsAzureAdAutomaticProv(array &$form, FormStateInterface $form_state) {
    $auto_create_user = $form_state->getValues()['azure_ad_auto_provisioning_operations']['operations']['create_user_azure_ad_automatic'];
    $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_automatic_provisioning_checkbox', $auto_create_user)->save();
    $this->messenger->addStatus($this->t('Configurations saved successfully.'));
  }

  /**
   * Manual sync.
   */
  public function moAzureManualSync(array &$form, FormStateInterface $form_state) {
    $entity_id = $form_state->getValues()['mo_azure_drupal_username'];

    $account = $this->entityTypeManager->getStorage('user')->load($entity_id);
    $entity_name = $account->getDisplayName();

    if (empty($entity_name)) {
      $this->messenger->addError($this->t('Please enter a valid entity (User or Role) to provision.'));
      return;
    }

    $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_manual_provisioning_checkbox', TRUE)->save();

    $this->moLogger->addLog('Provisioning on demand. Entered entity name is :' . $entity_name, __LINE__, __FUNCTION__, basename(__FILE__));

    $entity = $this->getEntityToProvision($entity_name);
    if (is_null($entity)) {
      $this->messenger->addError('"' . $entity_name . '" ' . $this->t('can not be recognized as a valid User or Role. Enter a valid entity (User or Role) to provision.'));
      return;
    }

    $mo_entity_handler = new moUserProvisioningOperationsHandler($entity);
    try {
      $result = $mo_entity_handler->insert();
      if (is_null($result)) {
        $url = $this->baseUrl . moUserProvisioningConstants::DRUPAL_LOGS_PATH;
        $this->messenger->addError(
              $this->t(
                  'An error occurred while provisioning the user, please refer to <a href=":url">drupal logs</a> for more information.',
                  [
                    ':url' => $url,
                  ]
              )
          );
        return;
      }
      // Now you can click on <strong>All Done</strong>button.
      $this->messenger->addMessage(
            $this->t(
                '@label successfully created at the configured application.',
                [
                  '@label' => $entity->label(),
                ]
            )
        );
    }
    catch (\Exception $exception) {
      $this->messenger->addError($exception->getMessage());
    }
  }

  /**
   * Get entity to provision.
   */
  private function getEntityToProvision(string $entity_name) {
    $users = NULL;
    try {
      $users = $this->entityTypeManager
        ->getStorage('user')
        ->loadByProperties(['name' => $entity_name]);
    }
    catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
      $this->moLogger->addLog('ERROR: user load by name' . $e->getMessage(), __LINE__, __FUNCTION__, basename(__FILE__));
    }

    $user = $users ? reset($users) : FALSE;

    if ($user) {
      return $user;
    }
    return NULL;
  }

  /**
   * Azure AD Overview Tab.
   */
  private function azureAdOverviewTab(array &$form, FormStateInterface $form_state) {
    $response = new RedirectResponse(Url::fromRoute('azure_ad.overview')->toString());
    $response->send();
    return new Response();
  }

  /**
   * Back to Provisioning types.
   */
  public function moAzureProvBackStep4Button() {
    $this->configFactory()->getEditable('azure_ad.settings')->set('azure_ad_provisioning_step', 'step3')->save();
    $response = new RedirectResponse(Url::fromRoute('azure_ad.overview')->toString());
    $response->send();
    return new Response();
  }

}
