<?php

namespace Drupal\miniorange_2fa\Plugin;

use Drupal\Component\Plugin\PluginBase;
use Drupal\miniorange_2fa\Plugin\AuthenticationTypeInterface;
use Drupal\user\Entity\User;
use Drupal\miniorange_2fa\MoAuthUtilities;
use Drupal\miniorange_2fa\MiniorangeCustomerProfile;
use Drupal\miniorange_2fa\AuthenticationAPIHandler;
use Drupal\miniorange_2fa\MiniorangeUser;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Url;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\miniorange_2fa\AuthenticationType;

/**
 * Base class for authentication type plugins.
 *
 */
abstract class AuthenticationTypePluginBase extends PluginBase implements AuthenticationTypeInterface {

  use StringTranslationTrait;
  use DependencySerializationTrait;

  public function getName(): string { return $this->pluginDefinition['name'] ?? $this->pluginDefinition['id']; }
  public function getCode(): string { return $this->pluginDefinition['code'] ?? strtoupper($this->pluginDefinition['id']); }
  public function getType(): string { return $this->pluginDefinition['type'] ?? 'OTHER'; }
  public function getDescription(): string { return $this->pluginDefinition['description'] ?? ''; }
  public function getSupportedDevices(): array { return $this->pluginDefinition['supported_devices'] ?? []; }
  public function requiresChallenge(): bool { return (bool) ($this->pluginDefinition['challenge'] ?? TRUE); }
  public function isOutOfBand(): bool { return (bool) ($this->pluginDefinition['oob'] ?? FALSE); }
  public function getIosLink(): string { return $this->pluginDefinition['ios_link'] ?? ''; }
  public function getAndroidLink(): string { return $this->pluginDefinition['android_link'] ?? ''; }

  /**
   * Converts the plugin definition to array.
   */
  public function toArray(): array {
    return [
      'id' => $this->pluginDefinition['id'] ?? '',
      'name' => $this->getName(),
      'code' => $this->getCode(),
      'type' => $this->getType(),
      'description' => $this->getDescription(),
      'supported_devices' => $this->getSupportedDevices(),
      'requires_challenge' => $this->requiresChallenge(),
      'is_out_of_band' => $this->isOutOfBand(),
      'ios_link' => $this->getIosLink(),
      'android_link' => $this->getAndroidLink(),
    ];
  }

  /**
   * Get the current user entity.
   *
   * @return User|null
   *   The current user entity or null if not found.
   */
  protected function getCurrentUser(): ?User {
    return User::load(\Drupal::currentUser()->id());
  }

  /**
   * Get user email from custom attributes.
   *
   * @param int $user_id
   *   The user ID.
   *
   * @return string
   *   The user email or empty string if not found.
   */
  protected function getUserEmail(int $user_id): string {
    $custom_attributes = MoAuthUtilities::get_users_custom_attribute($user_id);
    return $custom_attributes[0]->miniorange_registered_email ?? '';
  }

  /**
   * Get user phone number.
   *
   * @param int $user_id
   *   The user ID.
   *
   * @return string
   *   The user phone number or empty string if not found.
   */
  protected function getUserPhoneNumber(int $user_id): string {
    $phone_number = MoAuthUtilities::getUserPhoneNumber($user_id);
    return $phone_number ?? '';
  }

  /**
   * Create a new MiniorangeCustomerProfile instance.
   *
   * @return MiniorangeCustomerProfile
   *   The customer profile instance.
   */
  protected function createCustomerProfile(): MiniorangeCustomerProfile {
    return new MiniorangeCustomerProfile();
  }

  /**
   * Create a new AuthenticationAPIHandler instance.
   *
   * @return AuthenticationAPIHandler
   *   The authentication API handler instance.
   */
  protected function createAuthApiHandler(): AuthenticationAPIHandler {
    $customer = $this->createCustomerProfile();
    return new AuthenticationAPIHandler($customer->getCustomerID(), $customer->getAPIKey());
  }

  /**
   * Add error message using Drupal messenger.
   *
   * @param string $message
   *   The error message to display.
   */
  protected function addErrorMessage(string $message): void {
    \Drupal::messenger()->addError(t($message));
  }

  /**
   * Add status message using Drupal messenger.
   *
   * @param string $message
   *   The status message to display.
   */
  protected function addStatusMessage(string $message): void {
    \Drupal::messenger()->addStatus(t($message));
  }

  /**
   * Add warning message using Drupal messenger.
   *
   * @param string $message
   *   The warning message to display.
   */
  protected function addWarningMessage(string $message): void {
    \Drupal::messenger()->addWarning(t($message));
  }

  /**
   * Check if user session exists.
   *
   * @return bool
   *   TRUE if user session exists, FALSE otherwise.
   */
  protected function hasUserSession(): bool {
    $user = $this->getCurrentUser();
    return $user !== null;
  }

  /**
   * Validate user session and add error if not found.
   *
   * @return bool
   *   TRUE if user session is valid, FALSE otherwise.
   */
  protected function validateUserSession(): bool {
    if (!$this->hasUserSession()) {
      $this->addErrorMessage('User session not found. Please try again.');
      return false;
    }
    return true;
  }

  /**
   * Validate user email and add error if not found.
   *
   * @param int $user_id
   *   The user ID.
   *
   * @return bool
   *   TRUE if user email is valid, FALSE otherwise.
   */
  protected function validateUserEmail(int $user_id): bool {
    
    $session = MoAuthUtilities::getSession();
    $moMfaSession = $session->get('mo_auth');
    $user_email = $this->getUserEmail($user_id) ?: $moMfaSession['email'] ?? '';
    
    if (empty($user_email)) {
      $this->addErrorMessage('User email not found. Please contact administrator.');
      return false;
    }
    return true;
  }

  /**
   * Validate OTP passcode format.
   *
   * @param string $passcode
   *   The passcode to validate.
   *
   * @return bool
   *   TRUE if passcode is valid, FALSE otherwise.
   */
  protected function validatePasscode(string $passcode): bool {
    
    if (empty($passcode) || strlen($passcode) !== 6 || !ctype_digit($passcode)) {
      $this->addErrorMessage('Please enter a valid 6-digit code.');
      return false;
    }
    return true;
  }

  /**
   * Log error message for debugging.
   *
   * @param string $message
   *   The error message to log.
   * @param string $level
   *   The log level (error, warning, info).
   */
  protected function logError(string $message, string $level = 'error'): void {
    MoAuthUtilities::mo_add_loggers_for_failures($message, $level);
  }
  /**
   * Set form title and prefix with common structure.
   *
   * @param array &$form
   *   The form array to modify.
   * @param string $container_id
   *   The container ID for the form wrapper.
   */
  protected function setFormTitle(array &$form, string $container_id = 'auth-methods', string $auth_method_code = ''): void {
    $authMethodArray = !empty($auth_method_code) ? (AuthenticationType::getAuthType($auth_method_code) ?: $this->toArray()) : $this->toArray();
    $form['#title'] = t('Configure @method_name', ['@method_name' => $authMethodArray['name']]);
    $form['#prefix'] = '<div id="' . $container_id . '">';
    $status_messages = ['#type' => 'status_messages'];
    $form['#prefix'] .= \Drupal::service('renderer')->renderRoot($status_messages);
  }

  /**
   * Add form suffix to close container.
   *
   * @param array &$form
   *   The form array to modify.
   */
  protected function addFormSuffix(array &$form): void {
    $form['#suffix'] = '</div>';
  }

  /**
   * Create a MiniorangeUser instance for the current user.
   *
   * @param string $auth_method
   *   The authentication method code.
   *
   * @return MiniorangeUser|null
   *   The MiniorangeUser instance or null if validation fails.
   */
  protected function createMiniorangeUser(string $auth_method): ?MiniorangeUser {
    
    if (!$this->validateUserSession()) {
      return null;
    }

    $user = $this->getCurrentUser();
    if (!$this->validateUserEmail($user->id())) {
      return null;
    }

    $session = MoAuthUtilities::getSession();
    $moMfaSession = $session->get('mo_auth'); 
    $user_email = $this->getUserEmail($user->id()) ?: $moMfaSession['email'] ?? '';
   
    $customer = $this->createCustomerProfile();

    return new MiniorangeUser(  $customer->getCustomerID(),$user_email,null,
      null,
      $auth_method,
      $user_email
    );
  }

  /**
   * Create a redirect response to a specific route.
   *
   * @param string $route_name
   *   The route name to redirect to.
   * @param array $route_parameters
   *   Route parameters.
   * @param array $options
   *   Route options.
   *
   * @return AjaxResponse
   *   The AJAX response with redirect command.
   */
  protected function createRedirectResponse(string $route_name, array $route_parameters = [], array $options = []): AjaxResponse {
    $ajax_response = new AjaxResponse();
    $url = Url::fromRoute($route_name, $route_parameters, $options)->toString();
    $ajax_response->addCommand(new RedirectCommand($url));
    return $ajax_response;
  }

  /**
   * Validate API response and handle common errors.
   *
   * @param object $response
   *   The API response object.
   * @param string $context
   *   The context for logging.
   *
   * @return bool
   *   TRUE if response is valid, FALSE otherwise.
   */
  protected function validateApiResponse($response, string $context = ''): bool {
    if (!is_object($response)) {
      $this->logError("Invalid API response format in: $context", 'error');
      $this->addErrorMessage('An error occurred while processing your request. Please try again.');
      return false;
    }

    if ($response->status === 'FAILED') {
      $this->logError("API call failed in $context: " . ($response->message ?? 'Unknown error'), 'error');
      $this->addErrorMessage($response->message ?? 'An error occurred while processing your request.');
      return false;
    }

    return true;
  }

  /**
   * Get form values safely with default fallback.
   *
   * @param FormStateInterface $form_state
   *   The form state.
   * @param string $key
   *   The form value key.
   * @param mixed $default
   *   The default value if key not found.
   *
   * @return mixed
   *   The form value or default.
   */
  protected function getFormValue(FormStateInterface $form_state, string $key, $default = null) {
    return $form_state->getValue($key) ?? $default;
  }


  /**
   * Get configuration value from settings.
   *
   * @param string $key
   *   The configuration key.
   * @param mixed $default
   *   The default value if key not found.
   *
   * @return mixed
   *   The configuration value or default.
   */
  protected function getConfig(string $key, $default = null) {
    return \Drupal::config('miniorange_2fa.settings')->get($key) ?? $default;
  }

  /**
   * Set configuration value in settings.
   *
   * @param string $key
   *   The configuration key.
   * @param mixed $value
   *   The value to set.
   */
  protected function setConfig(string $key, $value): void {
    \Drupal::configFactory()->getEditable('miniorange_2fa.settings')->set($key, $value)->save();
  }
  
 /**
   * Get remaining transaction credits for all authentication types.
   *
   * @return array
   *   Array of remaining credits for SMS, Email, and IVR.
   */
  protected function getRemainingCredits(): array {
    $variables_and_values = ['mo_auth_2fa_ivr_remaining', 'mo_auth_2fa_sms_remaining', 'mo_auth_2fa_email_remaining'];
    return MoAuthUtilities::miniOrange_set_get_configurations($variables_and_values, 'GET');
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, \Drupal\Core\Form\FormStateInterface $form_state): array {      
    $this->setFormTitle($form);
    $this->addFormSuffix($form);
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, \Drupal\Core\Form\FormStateInterface $form_state): void {

  }

  /**
   * Update user authentication method in database and handle common workflow.
   *
   * @param string $auth_method
   *   The authentication method code.
   * @param \Drupal\miniorange_2fa\MiniorangeUser $miniorange_user
   *   The MiniorangeUser instance.
   * @param \Drupal\miniorange_2fa\UsersAPIHandler $user_api_handler
   *   The user API handler.
   * @param array $additional_fields
   *   Additional fields to update in the database (optional).
   * @param bool $replace_totp_methods
   *   Whether to replace other TOTP methods (default: false).
   * @param array $bulk_configure_methods
   *   Array of methods to configure together (for QR-based methods).
   *
   * @return array
   *   Result array with 'success' boolean and 'message' string.
   */
  protected function updateUserAuthenticationMethod(string $auth_method, $miniorange_user,$user_api_handler,array $additional_fields = [],bool $replace_totp_methods = false, array $bulk_configure_methods = []): array {
    
    if (!$this->validateUserSession()) {
      return ['success' => false, 'message' => 'User session validation failed.'];
    }

    $moMfaSession = MoAuthUtilities::getSession()->get('mo_auth', []); 
    $user =  $this->getCurrentUser() ?: User::load($moMfaSession['uid']) ?? null;

    $custom_attribute = MoAuthUtilities::get_users_custom_attribute($user->id());
    $configured_methods = MoAuthUtilities::mo_auth_get_configured_methods($custom_attribute);
  
    if ($replace_totp_methods) {
      $configured_methods = array_values(array_diff($configured_methods, MoAuthUtilities::mo_TOTP_2fa_mentods()));
    }
   
    if (!empty($bulk_configure_methods)) {
      foreach ($bulk_configure_methods as $method) {
        if (!in_array($method, $configured_methods)) {
          $configured_methods[] = $method;
        }
      }
    } else {
      if (!in_array($auth_method, $configured_methods)) {
        $configured_methods[] = $auth_method;
      }
    }

    $config_methods = implode(', ', $configured_methods);

    $api_response = $user_api_handler->update($miniorange_user);
    if (!is_object($api_response) || $api_response->status !== 'SUCCESS') {
      $this->logError('Failed to update user in miniOrange dashboard: ' . print_r($api_response, TRUE), 'error');
      return ['success' => false, 'message' => 'Failed to update user in miniOrange dashboard.'];
    }
  
    $admin_fields = [
      'activated_auth_methods' => $auth_method,
      'configured_auth_methods' => $config_methods,
    ];
    $enduser_fields = array(
      'uid' => $moMfaSession['uid']  ?? $user->id(),
      'configured_auth_methods' => AuthenticationType::$EMAIL['code'] . ', ' . $auth_method,
      'miniorange_registered_email' => $moMfaSession['email'] ?? '',
      'activated_auth_methods' => $auth_method,
      'enabled' => TRUE,
      'qr_code_string' => (isset($moMfaSession['mo_challenge_response']) && isset($moMfaSession['mo_challenge_response']->qrCodeData)) ? $moMfaSession['mo_challenge_response']->qrCodeData : '',
      'phone_number' => $miniorange_user->getPhone() ?: $moMfaSession['phone_number'] ?? '',
  );
    $database = \Drupal::database();
    $admin_fields = array_merge($admin_fields, $additional_fields);
    $custom_attribute = MoAuthUtilities::get_users_custom_attribute($user->id());
    
    if (count($custom_attribute) > 0) {
    
      $database->update('UserAuthenticationType')->fields($admin_fields)->condition('uid', $user->id(), '=')->execute();
    } else {
   
      $database->insert('UserAuthenticationType')->fields($enduser_fields)->execute();
    }
   
    $auth_type = AuthenticationType::getAuthType($auth_method);
    $method_name = $auth_type['name'] ?? $this->getName();
    return [
      'success' => true, 
      'message' => $this->t('@method_name configured successfully.', ['@method_name' => $method_name])
    ];
  }
  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, \Drupal\Core\Form\FormStateInterface $form_state): void {
    
  }

  protected function handlePostAuthenticationRedirection(bool $success,string $success_message = '',string $error_message = '',?FormStateInterface $form_state = null, array $options = [] ) {
    
    $success_route = $options['success_route'] ?? '';
    $success_route_params = $options['success_route_params'] ?? [];
    $error_route_registered = $options['error_route_registered'] ?? 'miniorange_2fa.setup_twofactor';
    $error_route_unregistered = $options['error_route_unregistered'] ?? 'user.page';
  
    if ($success && $success_message) {
      $this->addStatusMessage($success_message);
    }
    elseif (!$success && $error_message) {
      $this->addErrorMessage($error_message);
    }
  
    $session = MoAuthUtilities::getSession();
    $moMfaSession = $session->get("mo_auth") ?? [];
    $user = $this->getCurrentUser() ?: ($moMfaSession['uid'] ?? null);
  
    if (is_numeric($user)) {
      $user = User::load($user);
    }
    if (!$user) {
      return $this->performRedirect('user.login', [], $form_state);
    }
  
    $custom_attribute = MoAuthUtilities::get_users_custom_attribute($user->id());
    $has_registered_email = !empty($custom_attribute[0]->miniorange_registered_email ?? '');
  
    if ($success) {
      if ($has_registered_email) {
        $route = $success_route ?: 'miniorange_2fa.setup_twofactor';
        $params = $success_route_params;
      }
      else {
        if (!empty($moMfaSession['uid'])) {
          user_login_finalize(User::load($moMfaSession['uid']));
        }
        $route = $success_route ?: 'user.page';
        $params = $success_route_params;
      }
    }
    else {
      $route = $has_registered_email ? $error_route_registered : $error_route_unregistered;
      $params = [];
    }
  
    return $this->performRedirect($route, $params, $form_state);
  }
  
  /**
   * Perform redirect depending on form or AJAX context.
   */
  private function performRedirect(string $route, array $params, ?FormStateInterface $form_state) {
    if ($form_state) {
      $form_state->setRedirect($route, $params);
      return null;
    }
    return $this->createRedirectResponse($route, $params);
  }

}
