package dev.lions.config;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import dev.lions.exceptions.ConfigurationException;

import java.time.Duration;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Configuration Elasticsearch de l'application.
 * Cette classe gère l'ensemble des paramètres de configuration pour l'intégration
 * Elasticsearch de manière thread-safe et optimisée.
 *
 * @author Lions Dev Team
 * @version 2.0
 */
@Slf4j
@ApplicationScoped
@Getter
public class ElasticsearchConfig {

  private static final int DEFAULT_SHARDS = 5;
  private static final int DEFAULT_REPLICAS = 1;
  private static final String INDEX_PREFIX = "lions_";
  private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
  private static final int MAX_RETRY_COUNT = 3;

  @Inject
  @ConfigProperty(name = "app.environment")
  private String environment;

  /**
   * Configuration des connexions Elasticsearch.
   */
  @Getter
  public static class ConnectionConfig {
    @NotNull
    private String hosts;

    @Min(1)
    @Max(65535)
    private Integer port = 9200;

    private String username;
    private String password;
    private Boolean useSsl = false;

    @Min(1000)
    private Integer connectionTimeout = 5000;

    @Min(1000)
    private Integer socketTimeout = 60000;

    @Min(1000)
    private Integer maxRetryTimeout = 60000;

    private Map<String, String> additionalSettings = new HashMap<>();

    public void validate() {
      if (hosts == null || hosts.trim().isEmpty()) {
        throw new ConfigurationException("La configuration des hôtes Elasticsearch est requise");
      }
      if (port < 1 || port > 65535) {
        throw new ConfigurationException("Port invalide : " + port);
      }
      if (useSsl && (username == null || password == null)) {
        throw new ConfigurationException("Les identifiants sont requis en mode SSL");
      }
    }
  }

  /**
   * Configuration des index Elasticsearch.
   */
  @Getter
  public static class IndexConfig {
    private final Map<String, IndexSettings> indices = new ConcurrentHashMap<>();
    private final Map<String, Object> defaultSettings = new HashMap<>();
    private final Map<String, Object> analysisSettings = new HashMap<>();

    public IndexConfig() {
      initializeDefaultSettings();
      initializeAnalysisSettings();
    }

    private void initializeDefaultSettings() {
      defaultSettings.put("number_of_shards", DEFAULT_SHARDS);
      defaultSettings.put("number_of_replicas", DEFAULT_REPLICAS);
      defaultSettings.put("refresh_interval", "1s");
      defaultSettings.put("max_result_window", 10000);
    }

    private void initializeAnalysisSettings() {
      Map<String, Object> analyzer = new HashMap<>();
      analyzer.put("type", "custom");
      analyzer.put("tokenizer", "standard");
      analyzer.put("filter", List.of("lowercase", "asciifolding"));

      analysisSettings.put("analyzer", Map.of("default_analyzer", analyzer));
    }

    public void addIndex(String name, IndexSettings settings) {
      indices.put(name, settings);
    }

    public IndexSettings getIndexSettings(String name) {
      return indices.getOrDefault(name, createDefaultSettings());
    }

    private IndexSettings createDefaultSettings() {
      return new IndexSettings(DEFAULT_SHARDS, DEFAULT_REPLICAS);
    }
  }

  /**
   * Paramètres spécifiques à un index.
   */
  @Getter
  public static class IndexSettings {
    @Min(1)
    @Max(50)
    private final int numberOfShards;

    @Min(0)
    @Max(5)
    private final int numberOfReplicas;

    private final Map<String, Object> customSettings;

    public IndexSettings(int numberOfShards, int numberOfReplicas) {
      this.numberOfShards = numberOfShards;
      this.numberOfReplicas = numberOfReplicas;
      this.customSettings = new HashMap<>();
    }

    public void addCustomSetting(String key, Object value) {
      customSettings.put(key, value);
    }
  }

  private final ConnectionConfig connectionConfig = new ConnectionConfig();
  private final IndexConfig indexConfig = new IndexConfig();
  private final Map<String, Object> clientSettings = new HashMap<>();

  /**
   * Initialise la configuration Elasticsearch.
   */
  @PostConstruct
  public void initialize() {
    try {
      log.info("Initialisation de la configuration Elasticsearch");

      validateConfiguration();
      initializeIndices();
      configureClient();

      log.info("Configuration Elasticsearch initialisée avec succès");

    } catch (Exception e) {
      String message = "Erreur lors de l'initialisation de la configuration Elasticsearch";
      log.error(message, e);
      throw new ConfigurationException(message, e);
    }
  }

  /**
   * Valide la configuration Elasticsearch.
   */
  private void validateConfiguration() {
    log.debug("Validation de la configuration Elasticsearch");
    connectionConfig.validate();
    validateIndices();
  }

  /**
   * Valide la configuration des indices.
   */
  private void validateIndices() {
    indexConfig.getIndices().values().forEach(settings -> {
      if (settings.getNumberOfShards() < 1 || settings.getNumberOfShards() > 50) {
        throw new ConfigurationException(
            "Nombre de shards invalide : " + settings.getNumberOfShards());
      }
      if (settings.getNumberOfReplicas() < 0 || settings.getNumberOfReplicas() > 5) {
        throw new ConfigurationException(
            "Nombre de réplicas invalide : " + settings.getNumberOfReplicas());
      }
    });
  }

  /**
   * Initialise les indices avec leurs configurations spécifiques.
   */
  private void initializeIndices() {
    String prefix = getEnvironmentPrefix();

    IndexSettings projectSettings = new IndexSettings(3, isProduction() ? 2 : 1);
    projectSettings.addCustomSetting("refresh_interval", isProduction() ? "30s" : "1s");

    indexConfig.addIndex(prefix + "projects", projectSettings);
    indexConfig.addIndex(prefix + "analytics", new IndexSettings(2, 1));
  }

  /**
   * Configure le client Elasticsearch.
   */
  private void configureClient() {
    clientSettings.put("client.transport.sniff", true);
    clientSettings.put("client.transport.ignore_cluster_name", false);
    clientSettings.put("client.transport.ping_timeout", "5s");
    clientSettings.put("client.transport.nodes_sampler_interval", "5s");

    if (isProduction()) {
      enhanceProductionSettings();
    }
  }

  /**
   * Renforce les paramètres de sécurité en production.
   */
  private void enhanceProductionSettings() {
    clientSettings.put("xpack.security.enabled", true);
    clientSettings.put("xpack.security.transport.ssl.enabled", true);
    clientSettings.put("xpack.security.transport.ssl.verification_mode", "full");
  }

  /**
   * Récupère le préfixe d'environnement pour les noms d'index.
   */
  private String getEnvironmentPrefix() {
    return isProduction() ? "prod_" : "dev_";
  }

  /**
   * Vérifie si l'environnement est en production.
   */
  private boolean isProduction() {
    return "production".equals(environment);
  }

  /**
   * Récupère l'URL de connexion complète.
   */
  public String getConnectionUrl() {
    String protocol = connectionConfig.getUseSsl() ? "https" : "http";
    return String.format("%s://%s:%d", protocol, connectionConfig.getHosts(),
                         connectionConfig.getPort());
  }
}