<?php

namespace Drupal\user_provisioning\ProviderSpecific\EntityHandler;

use Drupal\Core\Entity\EntityInterface;
use Drupal\user\Entity\User;
use Drupal\user_provisioning\Helpers\moUserProvisioningAudits;
use Drupal\user_provisioning\Helpers\moUserProvisioningLogger;
use Drupal\user_provisioning\moUserProvisioningConstants;
use Drupal\user_provisioning\ProviderSpecific\Factory\moResourceFactoryInterface;
use Drupal\user_provisioning\ProviderSpecific\Factory\moUserFactory;
use Psr\Log\LoggerInterface;

/**
 * Handles user provisioning entity operations for configured providers.
 */
class moUserProvisioningUserHandler implements moUserProvisioningEntityHandlerInterface {

  /**
   * Current entity being provisioned.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   *   User or role entity.
   */
  private EntityInterface $entity;

  /**
   * Factory for provider API handlers, parsers, and response processors.
   *
   * @var \Drupal\user_provisioning\ProviderSpecific\Factory\moResourceFactoryInterface
   *   Provider resource factory.
   */
  private moResourceFactoryInterface $resourceFactory;

  /**
   * Audit helper for provisioning operations.
   *
   * @var \Drupal\user_provisioning\Helpers\moUserProvisioningAudits
   *   Audits helper instance.
   */
  private moUserProvisioningAudits $audits;

  /**
   * Logger channel for provisioning messages.
   *
   * @var \Psr\Log\LoggerInterface
   *   Drupal logger channel.
   */
  private LoggerInterface $logger;

  /**
   * Module logger utility for formatted logs.
   *
   * @var \Drupal\user_provisioning\Helpers\moUserProvisioningLogger
   *   Provisioning logger utility.
   */
  private moUserProvisioningLogger $moLogger;

  /**
   * Constructs the user provisioning entity handler.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   User or Role entity.
   */
  public function __construct(EntityInterface $entity) {
    $this->entity = $entity;
    $this->resourceFactory = new moUserFactory();
    $this->audits = new moUserProvisioningAudits();
    $this->logger = \Drupal::logger('user_provisioning');
    $this->moLogger = new moUserProvisioningLogger();
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function searchResource(): array {
    try {
      [
        $status_code,
        $content,
        $conflict,
      ] = $this->resourceFactory->getResponseProcessor()
        ->get($this->resourceFactory
          ->getApiHandler()->get($this->resourceFactory
            ->getParser()->search($this->getUser()->getEmail())));

      // Adding logs and audits.
      $this->moLogger->addFormattedLog(json_decode($content, TRUE), __LINE__, __FUNCTION__, 'The received user search response is:');
      $this->addUserAudit('Read', $status_code);
      $this->logger->info("body:" . $content);
      return [$status_code, $content, $conflict];
    }
    catch (\Exception $exception) {
      $this->logger->error(__FUNCTION__ . ': ' . $exception->getMessage());
      $this->addUserAudit('Read');
      return [$exception->getCode(), $exception->getMessage()];
    }
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function createResource() {
    try {
      [$status_code, $content, $conflict] = $this->searchResource();
      if ($conflict == moUserProvisioningConstants::SCIM_CONFLICT_UNDETERMINED
          || $conflict == moUserProvisioningConstants::AZURE_CONFLICT_UNDETERMINED
          || $conflict == moUserProvisioningConstants::OKTA_CONFLICT_UNDETERMINED
          || $conflict == moUserProvisioningConstants::KEYCLOAK_CONFLICT_UNDETERMINED
      ) {
        $this->addUserAudit('Create');
        throw new \Exception('Conflict. User already exists at the configured application.');
      }
      elseif ($conflict == moUserProvisioningConstants::SCIM_CONFLICT
          || $conflict == moUserProvisioningConstants::AZURE_CONFLICT
          || $conflict == moUserProvisioningConstants::OKTA_CONFLICT
          || $conflict == moUserProvisioningConstants::KEYCLOAK_CONFLICT
      ) {
        // @todo add the call to handle the update user call to update the user present at the configured application.
        $this->moLogger->addLog('Conflict occurred. User will be updated.', __LINE__, __FUNCTION__, __FILE__);
        throw new \Exception('Conflict. User already exist at the configured application.');
      }
      else {
        // Make api call to create the resource.
        try {
          [
            $create_status_code,
            $create_content,
          ] = $this->resourceFactory->getResponseProcessor()
            ->post($this->resourceFactory->getApiHandler()
              ->post($this->resourceFactory->getParser()
                ->post($this->getUser())));

          $this->moLogger->addFormattedLog(json_decode($create_content, TRUE), __LINE__, __FUNCTION__, 'The received user create response is:');
          $this->addUserAudit('Create', $create_status_code);
          return $create_content;
        }
        catch (\Exception $exception) {
          $this->logger->error(__FUNCTION__ . ': ' . $exception->getMessage());
          $this->addUserAudit('Create');
          return NULL;
        }
        // @todo process the POST response, store the received user id to db for future reference when you have to make a put or delete operation.
      }
    }
    catch (\Exception $exception) {
      $this->logger->error(__FUNCTION__ . ': ' . $exception->getMessage());
      $this->addUserAudit('Read');
      return NULL;
    }
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function updateResource() {
    // @todo Implement updateResource() method.
  }

  /**
   * {@inheritDoc}
   */
  public function deleteResource() {
    // @todo Implement deleteResource() method. Send the api request for deleting user, if not succeeded then add the operation in the Queue factory to be processed later.
  }

  /**
   * Returns the User object using the id of the entity of current object.
   *
   * @return \Drupal\user\Entity\User
   *   The loaded Drupal user entity.
   */
  private function getUser(): User {
    return User::load($this->entity->id());
  }

  /**
   * Adds a user-provisioning audit entry.
   *
   * @param string $operation
   *   Operation name.
   * @param int $status_code
   *   Status code for the executed operation.
   *   Default value handles the "FAILED" case.
   *
   * @return void
   */
  private function addUserAudit(string $operation, int $status_code = -1) {
    $this->audits->addAudit([$this->entity->id(),
      $this->entity->getAccountName(),
      time(),
      $operation,
      $status_code == -1 ? 0 : 1,
    ]);
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  public function deactivateResource() {
    // @todo Implement deactivateResource() method.
    try {
      [$status_code, $content, $conflict] = $this->searchResource();
      if ($conflict == moUserProvisioningConstants::SCIM_CONFLICT_UNDETERMINED || $conflict == moUserProvisioningConstants::OKTA_CONFLICT_UNDETERMINED) {
        $this->addUserAudit('DEACTIVATE');
        throw new \Exception('Conflict. User status is undefined.');
      }
      elseif ($conflict == moUserProvisioningConstants::SCIM_NO_CONFLICT || $conflict == moUserProvisioningConstants::OKTA_NO_CONFLICT) {
        // @todo add the call to handle the update user call to update the user present at the configured application.
        $this->moLogger->addLog('Conflict occurred. User will be updated.', __LINE__, __FUNCTION__, __FILE__);
        throw new \Exception('Conflict. User is not exists at the configured application.');
      }
      else {
        // Make api call to deactivate the resource.
        try {
          [
            $create_status_code,
            $create_content,
          ] = $this->resourceFactory->getResponseProcessor()
            ->deactivate($this->resourceFactory->getApiHandler()
              ->deactivate((array) $this->resourceFactory->getParser()
                ->deactivate($this->getUser())));

          $this->moLogger->addFormattedLog(json_decode($create_content, TRUE), __LINE__, __FUNCTION__, 'The received user deactivate response is:');
          $this->addUserAudit('DEACTIVATE', $create_status_code);

          return $create_content;
        }
        catch (\Exception $exception) {
          $this->logger->error(__FUNCTION__ . ': ' . $exception->getMessage());
          $this->addUserAudit('deactivate');
          throw $exception;
        }
        // @todo process the POST response, store the received user id to db for future reference when you have to make a put or delete operation.
      }
    }
    catch (\Exception $exception) {
      $this->logger->error(__FUNCTION__ . ': ' . $exception->getMessage());
      $this->addUserAudit('DEACTIVATE');
      return NULL;
    }
  }

}
