package com.ffii.core.utils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;

/** @author Alex */
public class CriteriaArgsBuilder {

  private HttpServletRequest request;
  private Map<String, Object> args;

  private CriteriaArgsBuilder(HttpServletRequest request, Map<String, Object> args) {
    this.args = args;
    this.request = request;
  }

  public static CriteriaArgsBuilder withRequest(HttpServletRequest request) {
    return new CriteriaArgsBuilder(request, new HashMap<String, Object>());
  }

  public static CriteriaArgsBuilder withRequestNMap(HttpServletRequest request, Map<String, Object> args) {
    return new CriteriaArgsBuilder(request, args);
  }

  public CriteriaArgsBuilder addStringExact(String paramName) throws ServletRequestBindingException {
    String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName));
    if (value != null)
      args.put(paramName, value);
    return this;
  }

  public CriteriaArgsBuilder addStringLike(String paramName) throws ServletRequestBindingException {
    String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName));
    if (value != null)
      args.put(paramName, "%" + value + "%");
    return this;
  }

  public CriteriaArgsBuilder addString(String paramName) throws ServletRequestBindingException {
    return this.addStringExact(paramName);
  }

  public CriteriaArgsBuilder addStringStartsWith(String paramName) throws ServletRequestBindingException {
    String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName));
    if (value != null)
      args.put(paramName, value + "%");
    return this;
  }

  public CriteriaArgsBuilder addStringEndsWith(String paramName) throws ServletRequestBindingException {
    String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName));
    if (value != null)
      args.put(paramName, "%" + value);
    return this;
  }

  public CriteriaArgsBuilder addStringList(String paramName) throws ServletRequestBindingException {
    String[] params = ServletRequestUtils.getStringParameters(this.request, paramName);
    if (params.length > 0) {
      List<String> value = new ArrayList<String>(params.length);
      for (String param : params)
        if (StringUtils.isNotBlank(param))
          value.add(param);
      if (value.size() > 0)
        args.put(paramName, value);
    }
    return this;
  }

  public CriteriaArgsBuilder addStringCsv(String paramName) throws ServletRequestBindingException {
    String text = ServletRequestUtils.getStringParameter(this.request, paramName);
    if (text != null && StringUtils.isNotEmpty(text))
      args.put(paramName, Arrays.asList(text.split(",")));
    return this;
  }

  public CriteriaArgsBuilder addInteger(String paramName) throws ServletRequestBindingException {
    Integer value = StringUtils.isNotBlank(this.request.getParameter(paramName))
        ? ServletRequestUtils.getRequiredIntParameter(request, paramName)
        : null;
    if (value != null)
      args.put(paramName, value);
    return this;
  }

  public CriteriaArgsBuilder addNonZeroInteger(String paramName) throws ServletRequestBindingException {
    Integer value = StringUtils.isNotBlank(this.request.getParameter(paramName))
        ? ServletRequestUtils.getRequiredIntParameter(request, paramName)
        : null;
    if (value != null && value.intValue() != 0)
      args.put(paramName, value);
    return this;
  }

  public CriteriaArgsBuilder addIntegerList(String paramName) throws ServletRequestBindingException {
    int[] params = ServletRequestUtils.getIntParameters(request, paramName);
    if (params.length > 0) {
      List<Integer> values = new ArrayList<Integer>();
      for (int param : params)
        values.add(param);
      args.put(paramName, values);
    }
    return this;
  }

  public CriteriaArgsBuilder addNonZeroIntegerList(String paramName) throws ServletRequestBindingException {
    int[] params = ServletRequestUtils.getIntParameters(request, paramName);
    if (params.length > 0) {
      List<Integer> values = new ArrayList<Integer>();
      for (int param : params)
        if (param != 0)
          values.add(param);
      args.put(paramName, values);
    }
    return this;
  }

  public CriteriaArgsBuilder addLong(String paramName) throws ServletRequestBindingException {
    Long value = StringUtils.isNotBlank(this.request.getParameter(paramName))
        ? ServletRequestUtils.getRequiredLongParameter(request, paramName)
        : null;
    if (value != null)
      args.put(paramName, value);
    return this;
  }

  public CriteriaArgsBuilder addNonZeroLong(String paramName) throws ServletRequestBindingException {
    Long value = StringUtils.isNotBlank(this.request.getParameter(paramName))
        ? ServletRequestUtils.getRequiredLongParameter(request, paramName)
        : null;
    if (value != null && value.longValue() != 0L)
      args.put(paramName, value);
    return this;
  }

  public CriteriaArgsBuilder addDatetime(String paramName) throws ServletRequestBindingException {
    String value = ServletRequestUtils.getStringParameter(request, paramName);
    if (StringUtils.isNotBlank(value)) {
      try {
        args.put(paramName, LocalDateTime.parse(value));
      } catch (DateTimeParseException e) {
        throw new ServletRequestBindingException(paramName);
      }
    }
    return this;
  }

  public CriteriaArgsBuilder addDatetime(String paramName, DateTimeFormatter formatter)
      throws ServletRequestBindingException {
    String value = ServletRequestUtils.getStringParameter(request, paramName);
    if (StringUtils.isNotBlank(value)) {
      try {
        args.put(paramName, LocalDateTime.parse(value, formatter));
      } catch (DateTimeParseException e) {
        throw new ServletRequestBindingException(paramName);
      }
    }
    return this;
  }

  public CriteriaArgsBuilder addDate(String paramName) throws ServletRequestBindingException {
    String value = ServletRequestUtils.getStringParameter(request, paramName);
    if (StringUtils.isNotBlank(value)) {
      try {
        args.put(paramName, LocalDate.parse(value));
      } catch (DateTimeParseException e) {
        throw new ServletRequestBindingException(paramName);
      }
    }
    return this;
  }

  public CriteriaArgsBuilder addDate(String paramName, DateTimeFormatter formatter)
      throws ServletRequestBindingException {
    String value = ServletRequestUtils.getStringParameter(request, paramName);
    if (StringUtils.isNotBlank(value)) {
      try {
        args.put(paramName, LocalDate.parse(value, formatter));
      } catch (DateTimeParseException e) {
        throw new ServletRequestBindingException(paramName);
      }
    }
    return this;
  }

  public CriteriaArgsBuilder addDateTo(String paramName) throws ServletRequestBindingException {
    String value = ServletRequestUtils.getStringParameter(request, paramName);
    if (StringUtils.isNotBlank(value)) {
      try {
        args.put(paramName, LocalDate.parse(value).plusDays(1));
      } catch (DateTimeParseException e) {
        throw new ServletRequestBindingException(paramName);
      }
    }
    return this;
  }

  public CriteriaArgsBuilder addDateTo(String paramName, DateTimeFormatter formatter)
      throws ServletRequestBindingException {
    String value = ServletRequestUtils.getStringParameter(request, paramName);
    if (StringUtils.isNotBlank(value)) {
      try {
        args.put(paramName, LocalDate.parse(value, formatter).plusDays(1));
      } catch (DateTimeParseException e) {
        throw new ServletRequestBindingException(paramName);
      }
    }
    return this;
  }

  public CriteriaArgsBuilder addBoolean(String paramName) throws ServletRequestBindingException {

    if (request.getParameter(paramName) == null || request.getParameter(paramName).isEmpty()) {
      return this;
    }
    Boolean value = ServletRequestUtils.getBooleanParameter(request, paramName);
    args.put(paramName, value);
    return this;
  }

  public CriteriaArgsBuilder put(String key, Object value) {
    args.put(key, value);
    return this;
  }

  public Map<String, Object> build() {
    return this.args;
  }
}
