<?php

namespace Drupal\csc_taxonomy\Form;

use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\taxonomy\Entity\Term;

/**
 * Defines the CscFormSetiings class.
 */
class CscFormSettings extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'csc_form_settings';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $countries_options = $this->getCountriesOptions();
    $form['actions'] = [
      '#type' => 'details',
      '#title' => $this->t('Charging options'),
      '#open' => TRUE,
    ];

    $form['actions']['description'] = [
      '#type' => 'markup',
      '#markup' => $this->t('To ensure proper loading, please load countries first, then departments or states, and finally cities or municipalities.'),
      '#prefix' => '<p>',
      '#suffix' => '</p>',
    ];

    $form['actions']['option_upload'] = [
      '#type' => 'select',
      '#title' => $this->t('Select the taxonomy to load'),
      '#options' => [
        "none" => $this->t('Seleccione'),
        "countries" => $this->t('Countries'),
        "states" => $this->t('States'),
        "cities" => $this->t('Cities'),
      ],
    ];

    $form['actions']['upload'] = [
      '#type' => 'submit',
      '#name' => 'upload',
      '#value' => $this->t('Upload'),
      '#submit' => ['::uploadData'],
    ];

    $form['delete'] = [
      '#type' => 'details',
      '#title' => $this->t('Removal options'),
      '#open' => TRUE,
    ];

    $form['delete']['option_delete'] = [
      '#type' => 'select',
      '#title' => $this->t('Select the taxonomy to delete'),
      '#options' => [
        "none" => $this->t('Seleccione'),
        "countries" => $this->t('Countries'),
        "states" => $this->t('States'),
        "cities" => $this->t('Cities'),
      ],
    ];

    $form['delete']['delete'] = [
      '#type' => 'submit',
      '#name' => 'delete',
      '#value' => $this->t('Delete'),
      '#submit' => ['::deleteData'],
    ];

    $form['individual_actions'] = [
      '#type' => 'details',
      '#title' => $this->t('Individual actions'),
      '#open' => TRUE,
    ];

    $form['individual_actions']['country'] = [
      '#type' => 'select',
      '#title' => $this->t('Select a country'),
      '#options' => $countries_options,
    ];

    return $form;
  }

  /**
   * Validate.
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {

  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * Upload data.
   */
  public function uploadData(array &$form, FormStateInterface $form_state) {
    $option = $form_state->getValue('option_upload');

    if ($option === "none") {
      $this->messenger()->addError($this->t('Please select a taxonomy.'));
      return;
    }

    $config = $this->getTaxonomyConfig()[$option];
    $csv_data = $this->readCsv($config['file']);

    if (empty($csv_data)) {
      $this->messenger()->addError($this->t('Error opening CSV or file does not exist.'));
      return;
    }

    if ($option == 'countries') {
      foreach ($csv_data as $row) {
        if (!$this->getCountryTermId($row[0])) {
          $this->createTerm('countries', $row[1], [$config['id_field'] => $row[0]]);
        }
      }
      $this->messenger()->addMessage($this->t('Countries loaded correctly.'));
    }
    else {
      $relations = $this->loadAllTermsByVocabulary($option === 'states' ? 'countries' : 'states');
      $countries_terms = $option === 'cities' ? $this->loadAllTermsByVocabulary('countries') : [];

      $batch = (new BatchBuilder())
        ->setTitle($this->t('Processing @option...', ['@option' => $option]))
        ->setFinishCallback([$this, 'batchFinished']);

      foreach (array_chunk($csv_data, 100) as $chunk) {
        $batch->addOperation([$this, 'processGenericBatch'], [$chunk, $option, $config, $relations, $countries_terms]);
      }

      batch_set($batch->toArray());
    }
  }

  /**
   * Delete data.
   */
  public function deleteData(array &$form, FormStateInterface $form_state) {
    $option = $form_state->getValue('option_delete');

    if ($option === 'none') {
      $this->messenger()->addError($this->t('Please select a taxonomy to delete.'));
      return;
    }

    // Obtener los IDs de los términos a eliminar.
    $query = \Drupal::entityQuery('taxonomy_term')
      ->condition('vid', $option)
      ->accessCheck(FALSE);
    $term_ids = $query->execute();

    if (empty($term_ids)) {
      $this->messenger()->addMessage($this->t('No terms found to delete in @option.', ['@option' => $option]));
      return;
    }

    // Configurar el batch para eliminar en bloques de 100.
    $batch = (new BatchBuilder())
      ->setTitle($this->t('Deleting @option...', ['@option' => $option]))
      ->setInitMessage($this->t('Starting the deletion of @option.', ['@option' => $option]))
      ->setProgressMessage($this->t('Deleting @option...', ['@option' => $option]))
      ->setFinishCallback([$this, 'batchDeleteFinished'], [$option]);

    foreach (array_chunk($term_ids, 100) as $chunk) {
      $batch->addOperation([$this, 'processDeleteBatch'], [$chunk]);
    }

    batch_set($batch->toArray());
  }

  /**
   * Reads the countries CSV and returns an array suitable for a select field.
   *
   * @return array
   *   An associative array with country IDs as keys and country names as values.
   */
  protected function getCountriesOptions() {
    $options = ['none' => $this->t('Select a country')];

    $csv_data = $this->readCsv('countries.csv');

    if (!empty($csv_data)) {
      foreach ($csv_data as $row) {
        // Asumiendo que $row[0] es el ID del país y $row[1] es el nombre.
        $options[$row[0]] = $row[1];
      }
    }

    return $options;
  }

  /**
   * Returns the taxonomy vocabularies configuration.
   *
   * This function provides the data needed to load or process
   * the taxonomies: countries, states, and cities.
   *
   * @return array
   *   An associative array with the configuration for each vocabulary.
   */
  protected function getTaxonomyConfig() {
    return [
      'countries' => [
        'file' => 'countries.csv',
        'id_field' => 'field_country_id',
        'reference_field' => NULL,
        'needs_country_reference' => FALSE,
      ],
      'states' => [
        'file' => 'states.csv',
        'id_field' => 'field_state_id',
        'reference_field' => 'field_country_reference',
        'needs_country_reference' => FALSE,
      ],
      'cities' => [
        'file' => 'cities.csv',
        'id_field' => 'field_city_id',
        'reference_field' => 'field_state_reference',
        'needs_country_reference' => TRUE,
      ],
    ];
  }

  /**
   * Creates a taxonomy term in the specified vocabulary.
   *
   * @param string $vocabulary
   *   The ID of the vocabulary where the term will be created.
   * @param string $name
   *   The name of the term to be created.
   * @param array $fields
   *   (Optional) An associative array of additional fields to assign to the term.
   *   Example: ['field_country_id' => 'CO', 'field_state_reference' => 5].
   */
  protected function createTerm(string $vocabulary, string $name, array $fields = []) : void {
    // Create a new term with the basic and additional fields.
    $term = Term::create([
      'vid' => $vocabulary,
      'name' => $name,
    ] + $fields);

    // Save the term to the database.
    $term->save();
  }

  /**
   * Gets the path of the module.
   */
  protected function getModulePath() {
    return $this->moduleHandler()->getModule('csc_taxonomy')->getPath();
  }

  /**
   * Access the module_handler service.
   */
  protected function moduleHandler() {
    return \Drupal::service('module_handler');
  }

  /**
   * Access the entity_type.manager service.
   */
  protected function entityTypeManager() {
    return \Drupal::service('entity_type.manager');
  }

  /**
   * Gets the ID of the deparment term by its ID.
   */
  protected function getCountryTermId($country_id) {
    $terms = $this->entityTypeManager()
      ->getStorage('taxonomy_term')
      ->loadByProperties([
        'vid' => 'countries',
        'field_country_id' => $country_id,
      ]);

    return $terms ? reset($terms)->id() : NULL;
  }

  /**
   * Load all terms of a taxonomy by keyword.
   */
  protected function loadAllTermsByVocabulary($vocabulary_id) {
    $terms = $this->entityTypeManager()
      ->getStorage('taxonomy_term')
      ->loadByProperties(['vid' => $vocabulary_id]);

    $terms_by_field = [];
    foreach ($terms as $term) {
      $field_key = $vocabulary_id === 'countries' ? 'field_country_id' : 'field_state_id';
      $terms_by_field[$term->get($field_key)->value] = $term->id();
    }

    return $terms_by_field;
  }

  /**
   * Reads a CSV file from the module's CSV directory and returns its content.
   *
   * Skips the header row and returns the data as a multidimensional array.
   *
   * @param string $file_name
   *   The name of the CSV file to read. Example: 'countries.csv'.
   *
   * @return array|null
   *   Returns a multidimensional array containing the CSV data, or NULL if the file does not exist.
   */
  protected function readCsv(string $file_name): ?array {
    $csv_file_path = $this->getModulePath() . '/csv/' . $file_name;

    if (!file_exists($csv_file_path)) {
      return NULL;
    }
    $csv_data = [];
    if (($handle = fopen($csv_file_path, 'r')) !== FALSE) {
      $is_first_row = TRUE;

      while (($data = fgetcsv($handle, 1000, ',')) !== FALSE) {
        if ($is_first_row) {
          $is_first_row = FALSE;
          continue;
        }
        $csv_data[] = $data;
      }

      fclose($handle);
    }

    return $csv_data;
  }

  /**
   * Batch operation to create taxonomy terms from a CSV chunk.
   *
   * This method processes a batch of CSV rows to create taxonomy terms
   * in the specified vocabulary. It also allows adding reference fields
   * to relate terms to their parent entities.
   */
  public function processGenericBatch(array $chunk, string $vocabulary, array $config, array $relations, array $countries_terms, array &$context) {
    foreach ($chunk as $row) {
      $fields = [$config['id_field'] => $row[0]];

      if ($config['reference_field'] && isset($relations[$row[2]])) {
        $fields[$config['reference_field']] = $relations[$row[2]];
      }

      if ($config['needs_country_reference'] && isset($countries_terms[$row[3]])) {
        $fields['field_country_reference'] = $countries_terms[$row[3]];
      }

      $this->createTerm($vocabulary, $row[1], $fields);
      $context['results']['processed'][] = $row[1];
    }
  }

  /**
   * Batch operation to delete taxonomy terms in chunks.
   *
   * This method deletes a group of taxonomy terms by their IDs as part of
   * a batch process. It updates the batch context to track the total number
   * of terms deleted.
   */
  public function processDeleteBatch(array $term_ids, array &$context) {
    $storage = $this->entityTypeManager()->getStorage('taxonomy_term');
    $terms = $storage->loadMultiple($term_ids);
    $storage->delete($terms);
    if (!isset($context['results']['deleted'])) {
      $context['results']['deleted'] = 0;
    }
    $context['results']['deleted'] += count($terms);
  }

  /**
   * Ends the batch process.
   */
  public function batchFinished($success, array $results, array $operations) {
    if ($success) {
      $count = count($results['processed'] ?? []);
      $this->messenger()->addMessage($this->t('Se han cargado @count registros correctamente.', ['@count' => $count]));
    }
    else {
      $this->messenger()->addError($this->t('Hubo un error al cargar los datos.'));
    }
  }

  /**
   * Batch finished callback for taxonomy term deletion.
   *
   * This method is executed when the batch deletion process is complete.
   * It displays a success or error message based on the outcome of the batch.
   */
  public function batchDeleteFinished($success, array $results, array $operations, $option) {
    if ($success) {
      $count = $results['deleted'] ?? 0;
      $this->messenger()->addMessage($this->t('Successfully deleted @count terms from @option.', [
        '@count' => $count,
        '@option' => $option,
      ]));
    }
    else {
      $this->messenger()->addError($this->t('An error occurred while deleting terms from @option.', [
        '@option' => $option,
      ]));
    }
  }

}
