<?php

namespace Drupal\user_provisioning\ProviderSpecific\Parsers\UserParser;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user_provisioning\Helpers\moProviderSpecificProvisioning;
use Drupal\user_provisioning\Helpers\moUserProvisioningLogger;
use Drupal\user_provisioning\ProviderSpecific\Parsers\moResourceParserInterface;

/**
 * Parses user resources for Azure AD provisioning requests.
 */
class moUserAzureParser implements moResourceParserInterface {

  /**
   * Logger for parser diagnostics.
   *
   * @var \Drupal\user_provisioning\Helpers\moUserProvisioningLogger
   *   Logger instance.
   */
  private moUserProvisioningLogger $moLogger;

  /**
   * Immutable Azure AD configuration.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   *   Read-only configuration object.
   */
  private ImmutableConfig $config;

  /**
   * Editable Azure AD configuration.
   *
   * @var \Drupal\Core\Config\Config
   *   Editable configuration object.
   */
  private Config $configFactory;

  /**
   * Constructs the Azure user parser.
   */
  public function __construct() {
    $this->moLogger = new moUserProvisioningLogger();
    $this->config = \Drupal::config('azure_ad.settings');
    $this->configFactory = \Drupal::configFactory()->getEditable('azure_ad.settings');
  }

  /**
   * {@inheritDoc}
   */
  public function search($resource_id): string {
    $this->moLogger->addLog("Creating search request parameter.", __LINE__, __FUNCTION__, __FILE__);
    return $resource_id;
  }

  /**
   * {@inheritDoc}
   */
  public function get($resource_id) {
    // @todo Implement get() method.
  }

  /**
   * {@inheritDoc}
   */
  public function post(EntityInterface $entity) {

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

    if (isset($email_attr)) {
      if ($email_attr == 'uid') {
        $mailNickname = $entity->id();
      }
      else {
        $mailNickname = $entity->get($email_attr)->value;
      }
    }

    if (isset($username_attr)) {
      if ($username_attr == 'uid') {
        $username = $entity->id();
      }
      else {
        $username = $entity->get($username_attr)->value;
      }
    }

    if ((\Drupal::service('email.validator')->isValid($mailNickname))) {
      $mailNickname = explode('@', $mailNickname)[0];
    }

    if ((\Drupal::service('email.validator')->isValid($username))) {
      $username = explode('@', $username)[0];
    }

    $drupal_username = $entity->getDisplayName();
    $user_info = $this->config->get('mo_azure_ad_' . $drupal_username . '_dependency');
    $this->configFactory->clear('mo_azure_ad_' . $drupal_username . '_dependency')->save();
    $prov_specific_provisioning = new moProviderSpecificProvisioning();

    $azure_password = isset($user_info) && !empty($user_info) ? $prov_specific_provisioning->decrypt_data($user_info, $drupal_username) : $this->generateStrongPassword();
    $userObject = '{
             "accountEnabled":true,
             "passwordProfile" : {
             "password": "' . $azure_password . '",
             "forceChangePasswordNextSignIn": true
             },
             "mailNickname": "' . $mailNickname . '",
            "passwordPolicies": "DisablePasswordExpiration"
        }';

    $userObject = Json::decode($userObject);
    $userObject['userPrincipalName'] = $username . '@' . $this->config->get('mo_azure_tenant_name');
    $userObject['displayName'] = $username;

    // Log the final user object, but redact the password to avoid leaking it.
    $logUserObject = $userObject;
    if (isset($logUserObject['passwordProfile']['password'])) {
      $logUserObject['passwordProfile']['password'] = '***REDACTED***';
    }
    $this->moLogger->addLog('Final user object: ' . print_r($logUserObject, TRUE), __LINE__, __FUNCTION__, __FILE__);
    $this->moLogger->addFormattedLog(['userObject' => $logUserObject], __LINE__, __FUNCTION__, __FILE__, 'Final User Object created.');
    return $userObject;
  }

  /**
   * Generates a cryptographically-strong random password.
   *
   * Azure AD password policies vary by tenant, but this generator
   * produces a mix of upper, lower, digits and symbols.
   */
  private function generateStrongPassword(int $length = 16): string {
    $upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $lower = 'abcdefghijklmnopqrstuvwxyz';
    $digits = '0123456789';
    $symbols = '!@#$%^&*-_+=';

    $all = $upper . $lower . $digits . $symbols;

    $password = '';
    $password .= $upper[random_int(0, strlen($upper) - 1)];
    $password .= $lower[random_int(0, strlen($lower) - 1)];
    $password .= $digits[random_int(0, strlen($digits) - 1)];
    $password .= $symbols[random_int(0, strlen($symbols) - 1)];

    for ($i = strlen($password); $i < $length; $i++) {
      $password .= $all[random_int(0, strlen($all) - 1)];
    }

    return str_shuffle($password);
  }

  /**
   * {@inheritDoc}
   */
  public function put(array $resource) {
    // @todo Implement put() method.
  }

  /**
   * {@inheritDoc}
   */
  public function patch(array $resource) {
    // @todo Implement patch() method.
  }

  /**
   * {@inheritDoc}
   */
  public function delete(array $resource) {
    // @todo Implement delete() method.
  }

  /**
   * {@inheritDoc}
   */
  public function deactivate(EntityInterface $entity) {
    // @todo Implement deactivate() method.
  }

}
