<?php

namespace Drupal\miniorange_2fa\Plugin;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\miniorange_2fa\MiniorangeUser;
use Drupal\miniorange_2fa\UsersAPIHandler;
use Drupal\miniorange_2fa\MoAuthUtilities;

/**
 * Provides common functionality for OTP-based authentication methods.
 */
abstract class OtpAuthenticationTypePluginBase extends AuthenticationTypePluginBase {

  use DependencySerializationTrait;

  private const FORM_CONTAINER_ID = 'otp-based-methods';

  private const OTP_VERIFICATION_MODE = 'OTP';

  private const MAX_OTP_LENGTH = 8;

  private const TX_ID_CONFIG_KEY = 'mo_auth_tx_id';

  private const ERRORS = [
    'phone_required' => 'Phone number is required.',
    'otp_required'   => 'OTP is required.',
    'tx_missing'     => 'Transaction ID not found. Please request a new OTP.',
    'validation_fail'=> 'Validation Failed. Please enter the correct OTP.',
  ];

  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
   
    $this->addMethodSpecificWarnings();
    $this->setFormTitle($form, self::FORM_CONTAINER_ID);
   
    $form['mo_otp_based_methods'] = [
      '#type' => 'container',
      '#attributes' => ['id' => self::FORM_CONTAINER_ID],
    ];

    $form_state->get('name') === self::OTP_VERIFICATION_MODE ? $this->buildVerificationForm($form['mo_otp_based_methods'])
    : $this->buildInitialForm($form['mo_otp_based_methods'], $form_state);

    $this->addFormSuffix($form);
    return $form;
  }

  /**
   * Build the initial OTP request form.
   *
   * @param array $form
   *   The form array to build.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   */
  protected function buildInitialForm(array &$form, FormStateInterface $form_state): void {
    $user = $this->getCurrentUser();
    $session = MoAuthUtilities::getSession();
    $moMfaSession = $session->get('mo_auth');
    $user_email = $this->getUserEmail($user->id())?: $moMfaSession['email'] ?? '';
    $phone_number = $this->getUserPhoneNumber($user->id());

    if ($email_field = $this->renderEmailField($user_email)) {
      $form['mo_otp_based_methods']['email'] = $email_field;
    }

    if ($this->shouldRenderPhoneField()) {
      $form['mo_otp_based_methods']['phone'] = [
        '#type' => 'tel',
        '#title' => $this->t('Phone Number'),
        '#default_value' => $phone_number,
        '#required' => TRUE,
        '#attributes' => [
          'id' => 'query_phone',
          'autocomplete' => 'on',
        ],
      ];
      $form['mo_otp_based_methods']['phone_full'] = [
        '#type' => 'hidden',
        '#default_value' => $phone_number,
      ];
    }

    $form['mo_otp_based_methods']['authTypeCode'] = [
      '#type' => 'hidden',
      '#value' => $this->getCode(),
    ];

    $form['mo_otp_based_methods']['action'] = ['#type' => 'actions'];
    $form['mo_otp_based_methods']['action']['request_otp'] = [
      '#type' => 'submit',
      '#submit' => [[$this, 'handleRequestOtp']],
      '#button_type' => 'primary',
      '#value' => $this->t('Request OTP'),
      '#ajax' => [
        'callback' => [$this, 'sendOtp'],
        'wrapper' => self::FORM_CONTAINER_ID,
      ],
    ];
  }

  /**
   * Build the OTP verification form.
   *
   * @param array $form
   *   The form array to build.
   */
  protected function buildVerificationForm(array &$form): void {
    $form['status_messages'] = [
      '#type' => 'status_messages',
      '#weight' => -10,
    ];

    $form['miniorange_OTP'] = [
      '#type' => 'textfield',
      '#title' => $this->t('OTP'),
      '#required' => TRUE,
      '#maxlength' => self::MAX_OTP_LENGTH,
      '#attributes' => [
        'placeholder' => $this->t('Enter passcode'),
      ],
    ];

    $form['action'] = ['#type' => 'actions'];
    $form['action']['verify_phone'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#name' => 'verify_phone',
      '#value' => $this->t('Verify'),
      '#ajax' => [
        'event' => 'click',
        'callback' => [$this, 'handleOtpVerification'],
        'wrapper' => self::FORM_CONTAINER_ID,
      ],
    ];

    $form['action']['resend_otp'] = [
      '#type' => 'submit',
      '#name' => 'resend_otp',
      '#value' => $this->t('Resend OTP'),
      '#limit_validation_errors' => [],
      '#submit' => [[$this, 'submitResendOtp']],
      '#ajax' => [
        'event' => 'click',
        'callback' => [$this, 'sendOtp'],
        'wrapper' => self::FORM_CONTAINER_ID,
      ],
    ];
  }
  /**
   * Handle Request OTP button click.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   */
  public function handleRequestOtp(array &$form, FormStateInterface $form_state): void {
    if ($form_state->hasAnyErrors() || !$this->validateUserSession()) {
      return;
    }
    $session          = MoAuthUtilities::getSession();
    $moMfaSession     = $session->get("mo_auth", null);
    $phone = '';
    if ($this->shouldRenderPhoneField()) {
      $phone_full = trim($form_state->getValue('phone_full') ?? '');
      $phone_input = trim($form_state->getValue('phone') ?? '');
      
      $phone = (!empty($phone_full) && strpos($phone_full, '+') === 0) ? $phone_full : $phone_input;

      if (empty($phone)) {
        $this->addErrorMessage(self::ERRORS['phone_required']);
        return;
      }
    }
    
    $moMfaSession['phone_number'] = $phone;
    $session->set("mo_auth", $moMfaSession);
      
      $form_state->set('name', self::OTP_VERIFICATION_MODE)
        ->set('phone', $phone)
        ->set('authTypeCode', $form_state->getValue('authTypeCode'))
        ->setRebuild();
  }
  

  public function submitResendOtp(array &$form, FormStateInterface $form_state): void {
    $form_state->setRebuild();
  }
  
  /**
   * Unified AJAX callback for OTP requests (send/resend).
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   *
   * @return array
   *   The rebuilt form array.
   */
  public function sendOtp(array &$form, FormStateInterface $form_state) {
    if ($form_state->hasAnyErrors()) {
      $form_state->setRebuild();
      return $form;
    }

    $miniorange_user = $this->createMiniorangeUser($this->getCode());
    
    if (!$miniorange_user) {
      $this->addErrorMessage('Failed to create user instance for OTP authentication.');
      $form_state->setRebuild();
      return $form;
    }
    
    $phone = trim($form_state->get('phone') ?? '') ?: ($this->shouldRenderPhoneField() ? $this->getUserPhoneNumber($this->getCurrentUser()->id()) : '');
    if ($phone) {
      $miniorange_user->setPhone($phone);
    }
    
    $response = $this->createAuthApiHandler()->challenge($miniorange_user);
      if ($this->validateApiResponse($response, 'OTP sending')) {
        $this->addStatusMessage(
          $this->buildOtpStatusMessage($form_state, $miniorange_user)
        );
        $this->setConfig(self::TX_ID_CONFIG_KEY, $response->txId);
        $form_state->setRebuild();
      }
      else {
        $this->handleOtpSendFailure();
      }
    
    return $form;
  }

  /**
   * Build OTP status message based on authentication method.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param \Drupal\miniorange_2fa\MiniorangeUser $user
   *   The MiniorangeUser instance.
   *
   * @return string
   *   The formatted status message.
   */
  protected function buildOtpStatusMessage(FormStateInterface $form_state, MiniorangeUser $user): string {
    
    $button_name = $form_state->getTriggeringElement()['#name'] ?? '';
    $send_type = $button_name === 'resend_otp' ? 'resent' : 'sent';
    $phone = $form_state->get('phone') ?? '';
    $auth_code = $this->getCode();
    
    $message_builders = [
      'SMS' => function () use ($send_type, $phone) {
        return $this->t('We have @send an OTP to @phone_number. Please enter the OTP to verify your phone number.', [
          '@send' => $send_type,
          '@phone_number' => $phone,
        ]);
      },
      'EMAIL' => function () use ($send_type, $user) {
        return $this->t('We have @send an OTP to @user_email. Please enter the OTP to verify your email.', [
          '@send' => $send_type,
          '@user_email' => $user->getEmail(),
        ]);
      },
      'PHONE VERIFICATION' => function () use ($phone) {
        return $this->t('You will receive phone call on @phone_number shortly, which prompts OTP. Please enter the OTP to verify your phone number.', [
          '@phone_number' => $phone,
        ]);
      },
      'SMS AND EMAIL' => function () use ($send_type, $user, $phone) {
        return $this->t('We have @send an OTP to @user_email and @phone_number. Please enter the OTP to verify your email and phone number.', [
          '@send' => $send_type,
          '@user_email' => $user->getEmail(),
          '@phone_number' => $phone,
        ]);
      },
    ];

    $message_builder = $message_builders[$auth_code] ?? function () use ($send_type) {
      return $this->t('OTP @send successfully.', ['@send' => $send_type]);
    };

    return $message_builder();
  }

  /**
   * Handle OTP send failure and display appropriate error message.
   */
  protected function handleOtpSendFailure(): void {
    $customer = $this->createCustomerProfile();
    $user_api_handler = new UsersAPIHandler(
      $customer->getCustomerID(),
      $customer->getAPIKey()
    );
    $response = $user_api_handler->fetchLicense();
    
    if ($response->status !== 'FAILED') {
      if ((isset($response->smsRemaining) && $response->smsRemaining == 0) || 
          (isset($response->emailRemaining) && $response->emailRemaining == 0) || 
          (isset($response->ivrRemaining) && $response->ivrRemaining == 0)) {
        $this->addErrorMessage(
          'The number of OTP transactions have exhausted. Please recharge your account with SMS/Email/IVR transactions.'
        );
      }
      else {
        $this->addErrorMessage('There was an unexpected error. Please try again.');
      }
    }
    else {
      $this->addErrorMessage(
        'Please upgrade to our premium plan or contact us for getting a trial of the module at drupalsupport@xecurify.com.'
      );
    }
  }

  /**
   * Validate and process OTP verification.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The AJAX response.
   */
  protected function validateAndProcessOtp(FormStateInterface $form_state): AjaxResponse {
 
    $ajax_response = new AjaxResponse();

    if (!$this->validateUserSession()) {
      $this->addErrorMessage('User session validation failed.');
      return $ajax_response;
    }

    $otp_token = str_replace(' ', '', 
      trim($this->getFormValue($form_state, 'miniorange_OTP') ?? ''));

    $transaction_id = $this->getConfig(self::TX_ID_CONFIG_KEY);
    if (empty($transaction_id)) {
      $this->addErrorMessage(self::ERRORS['tx_missing']);
      return $ajax_response;
    }
    $session = MoAuthUtilities::getSession();
    $moMfaSession = $session->get('mo_auth');
    $email = $this->getUserEmail($this->getCurrentUser()->id()) ?? $moMfaSession['email'] ?? '';
    $auth_method = $this->getCode();
    
    $customer = $this->createCustomerProfile();
    $miniorange_user = new MiniorangeUser($customer->getCustomerID(),$email,NULL,NULL,$auth_method,$email);

    $auth_api_handler = $this->createAuthApiHandler();
    $result = $auth_api_handler->validate(   $miniorange_user,$transaction_id, $otp_token,NULL);
   
    if ($result->status === 'FAILED') {
      $this->addErrorMessage(self::ERRORS['validation_fail']);
      return $ajax_response;
    }
    elseif(isset($result->message) && stripos($result->message, 'Invalid OTP provided. Please try again.') !== FALSE) {
      $this->addErrorMessage(self::ERRORS['validation_fail']);
      return $ajax_response;
    }

    $this->setConfig(self::TX_ID_CONFIG_KEY, '');
    return $this->updateUserAuthSettings($form_state);
  }

  /**
   * Update user authentication settings after successful OTP verification.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The AJAX response with redirect command.
   */
  protected function updateUserAuthSettings(FormStateInterface $form_state): AjaxResponse {
    $auth_method = $this->getCode();
    $customer = $this->createCustomerProfile();
    $user_api_handler = new UsersAPIHandler($customer->getCustomerID(), $customer->getAPIKey());
    $phone_number = $form_state->get('phone');

    $miniorange_user = $this->createMiniorangeUser($auth_method);
    if (!$miniorange_user) {
      return $this->handlePostAuthenticationRedirection(  false, '',   $this->t("Error while updating authentication method.")  );
    }

    $additional_fields = ['phone_number' => $phone_number];
    $result = $this->updateUserAuthenticationMethod(   $auth_method, $miniorange_user, $user_api_handler, $additional_fields    );

    return $this->handlePostAuthenticationRedirection( $result['success'], $result['message'], $result['message']);
  }

  /**
   * AJAX callback for OTP verification.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   *
   * @return array|\Drupal\Core\Ajax\AjaxResponse
   *   The form array or AJAX response.
   */
  public function handleOtpVerification(array &$form, FormStateInterface $form_state) {
    if ($form_state->hasAnyErrors()) {
      $form_state->setRebuild();
      return $form;
    }

    $form_values = $form_state->getValues();

    if ($form_state->get('name') === self::OTP_VERIFICATION_MODE) {
      $otp_token = trim($form_values['miniorange_OTP'] ?? '');
    
      if (empty($otp_token)) {
        $this->addErrorMessage($this->t(self::ERRORS['otp_required']));
        $form_state->setRebuild();
        return $form;
      }
    }

    $response = $this->validateAndProcessOtp($form_state);

    if ($response instanceof AjaxResponse && !empty($response->getCommands())) {
      return $response;
    }

    if ($this->shouldRenderPhoneField()) {
      $phone = trim($form_state->get('phone') ?? '');
      if (!empty($phone)) {
        $this->setConfig('mo_auth_phone_number', $phone);
      }
    }

    $auth_method = $this->getCode();
    $this->setConfig('mo_auth_method', $auth_method);

    $form_state->setRebuild();
    return $form;
  }

  /**
   * Check if phone field should be rendered.
   *
   * @return bool
   *   TRUE if phone field should be shown, FALSE otherwise.
   */
  protected function shouldRenderPhoneField(): bool {
    return TRUE;
  }



  protected function renderEmailField(string $email): array {
    return [];
  }
  
  /**
   * Override in child classes to show method-specific warnings.
   */
  protected function addMethodSpecificWarnings(): void {

  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {
    
    $button_name = $form_state->getTriggeringElement()['#name'] ?? '';
 
    if (in_array($button_name, ['request_otp', 'resend_otp'])) {
      return;
    }
    
    $is_verification = $form_state->get('name') === self::OTP_VERIFICATION_MODE;
    
    if (!$is_verification && $this->shouldRenderPhoneField() && empty(trim($this->getFormValue($form_state, 'phone') ?? ''))) {
      $form_state->setErrorByName('phone', $this->t(self::ERRORS['phone_required']));
    }

    if ($is_verification && $button_name !== 'resend_otp' && empty(trim($this->getFormValue($form_state, 'miniorange_OTP') ?? ''))) {
      $form_state->setErrorByName('miniorange_OTP', $this->t(self::ERRORS['otp_required']));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
    if ($form_state->get('name') !== self::OTP_VERIFICATION_MODE) {
      $form_state->set('name', self::OTP_VERIFICATION_MODE)
        ->set('phone', $form_state->getValue('phone_full') ?? '')
        ->set('authTypeCode', $form_state->getValue('authTypeCode'))
        ->setRebuild();
      return;
    }

    if ($form_state->getTriggeringElement()['#name'] === 'resend_otp') {
      $form_state->setRebuild();
      return;
    }

    if ($this->shouldRenderPhoneField() && $phone = trim($form_state->get('phone') ?? '')) {
      $this->setConfig('mo_auth_phone_number', $phone);
    }

    $this->setConfig('mo_auth_method', $this->getCode());
  }

}
