package dev.lions.utils;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import dev.lions.exceptions.TemplateException;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.function.Function;

/**
 * Processeur de modèles de contenu.
 * Cette classe gère le remplacement des variables dans les modèles
 * avec support pour la validation et les transformations personnalisées.
 */
@Slf4j
@ApplicationScoped
public class TemplateProcessor {

  private static final Pattern VARIABLE_PATTERN =
      Pattern.compile("\\{\\{\\s*(\\w+)(?:\\|\\s*(\\w+))?\\s*\\}\\}");
  private static final int MAX_RECURSION_DEPTH = 10;

  @Inject
  @ConfigProperty(name = "template.default-encoding", defaultValue = "UTF-8")
  private String defaultEncoding;

  private final Map<String, Function<String, String>> transformers;
  private final Map<String, String> parameters;

  /**
   * Constructeur initialisant les transformateurs par défaut.
   */
  public TemplateProcessor() {
    this.transformers = initializeTransformers();
    this.parameters = new HashMap<>();
  }

  /**
   * Configure les paramètres du modèle.
   *
   * @param parameters Paramètres à utiliser
   * @return Instance courante pour chaînage
   */
  public TemplateProcessor withParameters(@NotNull Map<String, String> parameters) {
    this.parameters.clear();
    this.parameters.putAll(parameters);
    return this;
  }

  /**
   * Traite un modèle avec les paramètres configurés.
   *
   * @param template Contenu du modèle
   * @param parameters
   * @return Contenu traité
   */
  public String process(@NotBlank String template, Map<String, String> parameters) {
    log.debug("Traitement du modèle (longueur: {})", template.length());

    try {
      validateTemplate(template);
      String result = processTemplate(template, 0);
      log.debug("Modèle traité avec succès");
      return result;

    } catch (Exception e) {
      log.error("Erreur lors du traitement du modèle", e);
      throw new TemplateException("Erreur de traitement du modèle", e);
    }
  }

  /**
   * Valide le contenu du modèle.
   */
  private void validateTemplate(String template) {
    if (template == null || template.trim().isEmpty()) {
      throw new TemplateException("Le contenu du modèle ne peut pas être vide");
    }

    // Vérifie la syntaxe des variables
    Matcher matcher = VARIABLE_PATTERN.matcher(template);
    while (matcher.find()) {
      String variable = matcher.group(1);
      String transformer = matcher.group(2);

      if (transformer != null && !transformers.containsKey(transformer)) {
        throw new TemplateException(
            "Transformateur inconnu: " + transformer);
      }
    }
  }

  /**
   * Traite le modèle de manière récursive.
   */
  private String processTemplate(String template, int depth) {
    if (depth > MAX_RECURSION_DEPTH) {
      throw new TemplateException(
          "Profondeur maximale de récursion atteinte");
    }

    StringBuffer result = new StringBuffer();
    Matcher matcher = VARIABLE_PATTERN.matcher(template);

    while (matcher.find()) {
      String variable = matcher.group(1);
      String transformer = matcher.group(2);
      String replacement = getReplacement(variable, transformer);
      matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
    }
    matcher.appendTail(result);

    // Traite les variables imbriquées
    String processed = result.toString();
    if (processed.contains("{{")) {
      return processTemplate(processed, depth + 1);
    }

    return processed;
  }

  /**
   * Obtient la valeur de remplacement pour une variable.
   */
  private String getReplacement(String variable, String transformer) {
    String value = parameters.getOrDefault(variable, "");

    if (!parameters.containsKey(variable)) {
      log.warn("Variable non définie dans le modèle : {}", variable);
    }

    if (transformer != null) {
      Function<String, String> transform = transformers.get(transformer);
      if (transform != null) {
        value = transform.apply(value);
      }
    }

    return value;
  }

  /**
   * Initialise les transformateurs par défaut.
   */
  private Map<String, Function<String, String>> initializeTransformers() {
    Map<String, Function<String, String>> transforms = new HashMap<>();

    transforms.put("upper", String::toUpperCase);
    transforms.put("lower", String::toLowerCase);
    transforms.put("capitalize", this::capitalizeFirst);
    transforms.put("trim", String::trim);
    transforms.put("html", this::escapeHtml);

    return transforms;
  }

  /**
   * Met en majuscule la première lettre.
   */
  private String capitalizeFirst(String value) {
    if (value == null || value.isEmpty()) {
      return value;
    }
    return Character.toUpperCase(value.charAt(0)) +
           (value.length() > 1 ? value.substring(1) : "");
  }

  /**
   * Échappe les caractères HTML spéciaux.
   */
  private String escapeHtml(String value) {
    return value
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace("\"", "&quot;")
        .replace("'", "&#39;");
  }

  /**
   * Ajoute un transformateur personnalisé.
   */
  public void addTransformer(
      @NotBlank String name,
      @NotNull Function<String, String> transformer) {

    log.debug("Ajout du transformateur : {}", name);
    transformers.put(name, transformer);
  }

  /**
   * Vérifie la validité du modèle et des paramètres.
   */
  public boolean validate(@NotBlank String template) {
    try {
      validateTemplate(template);
      Matcher matcher = VARIABLE_PATTERN.matcher(template);

      while (matcher.find()) {
        String variable = matcher.group(1);
        if (!parameters.containsKey(variable)) {
          log.warn("Paramètre manquant : {}", variable);
          return false;
        }
      }

      return true;

    } catch (Exception e) {
      log.error("Validation du modèle échouée", e);
      return false;
    }
  }
}